Customer feedback analytics

EchoLens

Stop Sifting Start Shipping

EchoLens is a lightweight feedback analytics platform for startup and SMB product managers and CX leads who ship weekly. It ingests reviews, emails, tickets, and chats, auto-clusters themes with confidence scores, and outputs a ranked queue with one-click Jira/Linear sync. A live theme map updates in real time, cutting triage 60% and pushing high-impact fixes to the top.

Subscribe to get amazing product ideas like this one delivered daily to your inbox!

EchoLens

Product Details

Explore this AI-generated product idea in detail. Each aspect has been thoughtfully created to inspire your next venture.

Vision & Mission

Vision
Enable every product team to transform feedback into undeniable evidence and ship the right improvements with decisive speed.
Long Term Goal
Within four years, become the default feedback brain for 10,000 product teams, achieving 90% channel coverage and cutting discovery-to-delivery cycle times 50% through evidence-driven prioritization.
Impact
For startup and SMB product managers and CX leads drowning in feedback, EchoLens reduces triage time 60%, cuts unknown backlog 40%, ships high-impact fixes two sprints sooner, trims duplicate requests 30%, and accelerates CX responses 25% with evidence-backed priorities.

Problem & Solution

Problem Statement
Startup and SMB product managers and CX leads shipping weekly drown in reviews, emails, tickets, and chats, struggling to distill trustworthy themes and priorities. Heavy dashboards and brittle spreadsheets miss nuance, slow triage, and stall evidence-backed decisions.
Solution Overview
EchoLens ingests reviews, emails, tickets, and chats, auto-clusters related comments into labeled themes with confidence scores, and outputs a ranked queue so PMs act on what matters first. A live theme map updates as feedback arrives, and one-click sync creates linked Jira/Linear tickets, cutting triage time.

Details & Audience

Description
EchoLens is a lightweight customer feedback analytics platform that turns scattered reviews, emails, tickets, and chats into clear, prioritized insights. Built for startup and SMB product managers and CX leads who ship weekly and drown in feedback. It cuts triage time 60% and surfaces a ranked queue so teams act on what matters first. A live theme map visualizes clusters in real time as new comments arrive.
Target Audience
Product managers and CX leads (25-45) at software startups/SMBs, drowning in feedback, shipping weekly.
Inspiration
Tuesday sprint review: our PM dragged 300 Intercom snippets into a spreadsheet, midnight color codes bleeding together. Next morning, engineers argued anecdotes while a checkout bug screamed from App Store reviews we hadn’t even opened. The gut punch: feedback isn’t scarce—signal is. I hacked a quick clusterer; three themes lit up, checkout rose to #1, fixed by day’s end. That clarity became EchoLens.

User Personas

Detailed profiles of the target users who would benefit most from this product.

R

Renewal-Rescuer Casey

- Customer Success Manager owning 30–60 SMB accounts - 5–8 years B2B SaaS renewals and expansions - Remote-first in North America; overlaps CST/EST - Stack: Salesforce, Gainsight, Slack, Gong, Jira - OTE $110k–$150k; NRR-tied compensation

Background

Started in support, moved to CSM after rescuing at-risk logos with bug pattern analysis. Built renewal playbooks grounded in real user evidence; now needs automated, reliable signal.

Needs & Pain Points

Needs

1. Early warning on churn-driving product defects 2. Segment themes by account, ARR, renewal date 3. One-click escalations with customer evidence

Pain Points

1. Critical bugs discovered during renewal calls 2. Disjointed signals across tickets, emails, calls 3. Slow engineering follow-through on escalations

Psychographics

- Lives by NRR, hates preventable churn surprises - Pragmatic, action-first, data checks emotion - Advocates for customers inside engineering rooms - Compulsively closes loops, celebrates saved accounts

Channels

1. Slack alerts 2. Salesforce dashboards 3. Gong snippets 4. Gmail digests 5. LinkedIn groups

O

Onboarding-Optimizer Omar

- Growth/Onboarding PM at Series A–B SaaS - 4–7 years product/growth; ex-analyst - Team: 1 designer, 5 engineers, 1 analyst - Stack: Mixpanel, Segment, Notion, Slack, Jira/Linear - KPIs: Activation rate, time-to-value, onboarding CSAT

Background

Led a signup overhaul after shadowing support chats and NPS verbatims. Learned qualitative signals precede metric drops and wants continuous, automated intake.

Needs & Pain Points

Needs

1. Rank onboarding frictions by impact and confidence 2. Map themes to journey stages 3. Rapid Jira tickets with reproduction context

Pain Points

1. Funnels show drop-offs, not underlying reasons 2. Scattered anecdotes slow prioritization 3. Manual tagging is inconsistent, untrusted

Psychographics

- Obsessed with first-session aha moments - Balances quant funnels with qualitative texture - Experiments fast, kills ideas faster - Champions low-effort, high-impact fixes

Channels

1. Slack channels 2. Notion roadmaps 3. Product Hunt threads 4. LinkedIn posts 5. YouTube talks

Q

Quality-Quester Quinn

- QA Lead overseeing regression and release sign-off - 6–10 years testing; manual + automation hybrid - SMB SaaS, weekly releases across microservices - Tools: TestRail, Jira, Sentry, Slack, GitHub - Targets: Escaped defects, MTTR, release stability

Background

Transitioned from manual to hybrid QA after a costly regression escaped. Built incident reviews; now seeks earlier signals from user channels.

Needs & Pain Points

Needs

1. Early spike alerts on crash/bug themes 2. Reproduction steps aggregated from user reports 3. Link clustered issues to test cases

Pain Points

1. Late discovery of critical regressions post-release 2. Noisy, duplicate bug reports 3. Missing context for reliable reproduction

Psychographics

- Zero tolerance for recurring regressions - Trusts evidence, reproductions over opinions - Prevents fire drills with early detection - Methodical, checklist-driven, calm under pressure

Channels

1. Jira boards 2. Slack incidents 3. Sentry alerts 4. GitHub issues 5. TestRail runs

R

Review-Radar Rina

- Product Marketing Manager monitoring public reviews and ratings - 5–9 years PMM/Comms; launch and PR experience - Mobile and web apps; global user base - Tools: App Store Connect, Google Play Console, G2, Notion, Slack - KPIs: Star rating, review velocity, sentiment trend

Background

Navigated a one-star storm by patterning complaints, reframing messaging, and rallying fixes; now prefers continuous vigilance over fire drills.

Needs & Pain Points

Needs

1. Surface review themes tied to versions 2. Auto-route urgent PR risks to owners 3. Phrase bank from user wording

Pain Points

1. Overnight rating drops after releases 2. Manual review scraping across platforms 3. Inconsistent response tone and speed

Psychographics

- Reputation guardian, proactive not reactive - Seeks signal beneath loud outliers - Aligns messaging with lived user language - Keeps leadership calm with crisp summaries

Channels

1. App Store Connect alerts 2. Google Play Console reviews 3. G2 notifications 4. X brand mentions 5. Slack war-room

P

Prototype-Proving Priya

- Senior Product Designer running scrappy research - 6–12 years UX; research-ops savvy - SMB SaaS; design team of 3–5 - Tools: Figma, UserTesting, Notion, Slack, Jira - KPI: Usability issues resolved per cycle; UX debt burn-down

Background

Moved from agency sprints to in-house systems after seeing support threads predict usability issues. Built lightweight evidence rituals to speed design decisions.

Needs & Pain Points

Needs

1. Cluster usability complaints by task and screen 2. Evidence-rich threads to persuade engineers 3. Trendlines to validate design improvements

Pain Points

1. Designers guessing without fresh user language 2. Pushback without hard evidence 3. Feedback scattered across tools

Psychographics

- Empathy-driven, ruthless about friction removal - Believes real words beat lab scripts - Values fast, iterative validation - Shares artifacts to align the team

Channels

1. Figma comments 2. Slack design 3. Notion research 4. UserTesting highlights 5. Jira design

D

Deal-Defender Dylan

- Account Executive selling mid-market SaaS deals - 4–8 years sales; technically fluent - Territory: North America; hybrid, travel as needed - Tools: Salesforce, Gong, Slack, Gmail, Notion - KPIs: Win rate, cycle length, competitive loss reasons

Background

Missed quota after repeated blockers, then cataloged patterns from calls and emails. Now needs automated aggregation and status tracking to escalate and reassure prospects.

Needs & Pain Points

Needs

1. Rank deal-blocking themes across open pipeline 2. Shareable evidence to influence roadmap 3. Fast status updates for prospects

Pain Points

1. Repeating blockers across multiple deals 2. Weak internal case for fixes 3. Slow feedback loop from product

Psychographics

- Competitive, consultative, solution-oriented - Hates losing to avoidable friction - Values credibility backed by customer evidence - Moves fast, communicates crisply

Channels

1. Salesforce opportunity notes 2. Gong call snippets 3. Slack sales 4. LinkedIn DMs 5. Gmail updates

Product Features

Key capabilities that make this product valuable to its target users.

Adaptive Baselines

Learns normal feedback levels per segment (plan, region, version, channel, time-of-day) and auto-adjusts thresholds for seasonality and launches. Cuts false alarms while surfacing true anomalies earlier, boosting trust and reducing alert fatigue.

Requirements

Segmented Baseline Learning Engine
"As a product manager, I want EchoLens to learn normal feedback levels per segment so that detected anomalies reflect real deviations rather than noise."
Description

Implements an online time-series modeling service that learns normal feedback volumes and rates per segment (plan, region, version, channel, time-of-day) and per theme, capturing daily/weekly seasonality and trend. Supports incremental updates from streaming sources and batch backfill, maintains rolling windows, and outputs baseline mean and variance with confidence intervals for each segment–theme pair. Handles cold starts via hierarchical priors and minimum-sample gating. Exposes an internal API for baseline retrieval and persists outputs for downstream thresholding, anomaly detection, and visualization within EchoLens.

Acceptance Criteria
Real-time Streaming Update Adjusts Baseline Within SLA
Given a segment–theme pair with an existing baseline and a continuous stream of feedback events When at least 20 new events arrive or 5 seconds elapse since the last update, whichever comes first Then the engine updates the baseline mean, variance, and 95% CI within 2 seconds (p95) of the trigger And the update reflects exactly the new events within the rolling window without double-counting or omission And windowed aggregates drop events older than the configured window end within 2 seconds (p95)
Batch Backfill Recomputes Historical Baselines Without Data Loss
Given a validated backfill dataset for a date range [T0, T1] When a backfill job is executed for that range with a fixed model_version Then baselines for [T0, T1] are recomputed deterministically with identical outputs across repeated runs (checksum match) And any events overlapping with streaming data within [T0, T1] are de-duplicated by event_id And boundary continuity is preserved such that the mean at T0 after backfill differs from the pre-backfill mean by <= 1% for identical input And a single rerun with the same inputs produces no additional writes (idempotent upsert)
Cold Start with Hierarchical Priors and Minimum-Sample Gating
Given a new segment–theme pair with fewer than 50 events or less than 7 days of history When a baseline is requested for this key Then the engine emits a provisional baseline using hierarchical priors blended with observed data with prior weight >= 50% And the response includes status=provisional and a 95% CI width not narrower than the prior CI And upon reaching >= 50 events and >= 7 days of history, the next update sets status=stable with prior weight <= 10%
Seasonality and Trend Capture for Daily/Weekly Patterns
Given 90 days of historical hourly data exhibiting daily and weekly seasonality When the engine fits/updates the baseline model Then on a 14-day holdout, >= 95% of hourly actual counts fall within the predicted 95% CI And the mean absolute percentage error (MAPE) of the baseline mean over the holdout is <= 15% And the estimated weekly seasonal index correlates with the actual weekly pattern with Pearson r >= 0.8
Baseline Retrieval API Responds with Complete Schema and Performance SLA
Given a GET /internal/baselines request with theme_id, segment keys {plan, region, version, channel, hour_of_day}, and a UTC window [ts_start, ts_end] When the request is authorized and parameters are valid Then the API responds 200 within 200 ms p95 including fields {theme_id, segment, window_start, window_end, mean, variance, ci_low_95, ci_high_95, model_version, last_updated_at, status} And returns 404 for unknown keys, 400 for invalid parameters, and 401 for missing/invalid auth And all timestamps are ISO-8601 UTC and numeric fields are double precision
Persistence and Downstream Availability
Given a newly computed baseline record for a segment–theme window When the engine persists the result Then the record is written exactly-once via upsert on (theme_id, segment, window_start) and is durable And the record is available to downstream consumers via CDC/pub-sub within 60 seconds p95 And stored data passes schema validation and is retained for >= 180 days with periodic compaction preserving latest value per window
Segment–Theme Coverage and Cardinality Limits
Given observed combinations across {plan, region, version, channel, hour_of_day} When up to 1,000,000 active segment–theme keys are present Then the engine maintains baselines for all keys within the configured memory budget and enforces a cap by evicting least-recently-updated keys beyond the cap And metrics expose active_keys, evictions, and memory_usage with active_keys error <= 1% versus ground truth And requests for evicted or unseen keys return status=not_ready within 50 ms
Adaptive Thresholding per Segment
"As a CX lead, I want thresholds to auto-adjust for each segment so that I don’t have to maintain static rules and I get fewer false alarms."
Description

Computes dynamic alert thresholds per segment–theme using baseline distributions and configurable sensitivity, producing upper/lower bounds and anomaly scores. Applies minimum-volume filters, caps false-positive rates, and supports time-of-day sensitivity. Publishes thresholds to the detection service and updates them continuously as baselines evolve, eliminating manual rule-tuning while reducing alert fatigue.

Acceptance Criteria
Threshold Generation per Segment–Theme with Configurable Sensitivity
Given historical feedback counts for segment S=(plan=Pro, region=EU, version=2.3, channel=Email, time-of-day enabled) and theme T="Payment Failure" over the last 90 days And sensitivity is set to 0.7 When thresholds are computed for S,T Then the output includes lower_bound, upper_bound, and anomaly_score in the range [0,1] And lower_bound >= 0 and lower_bound < median_baseline < upper_bound And observations strictly above upper_bound or strictly below lower_bound are labeled anomaly=true by the detection service using the published thresholds And when sensitivity is increased from 0.7 to 0.9 on the same data, the count of anomalies flagged over the past 30 days is non-decreasing
Minimum Volume Filter Suppresses Low-Signal Windows
Given min_volume_per_window is configured to 20 events for S,T And a 15-minute evaluation window arrives with 12 events When anomaly evaluation runs for that window Then no anomaly is emitted for S,T in that window And thresholds/baselines are not updated from this window And a min_volume_skipped metric is incremented for S,T When a subsequent window has exactly 20 events Then normal thresholding and scoring are applied
False-Positive Rate Cap on Backtest Windows
Given a labeled validation set for S,T containing N_non_anomalous windows from the last 30 days And FPR_cap is configured to 5% When thresholds are calibrated using this cap Then the observed false-positive rate on the non-anomalous windows is <= 6% (cap + 1% tolerance) And if the observed false-positive rate exceeds 6%, calibration retries with adjusted bounds until the cap is met or a calibration_failed event is emitted
Time-of-Day Sensitive Thresholding by Hour Bins
Given time-of-day sensitivity is enabled with 24 hourly bins for S,T And historical data exhibits diurnal variation When thresholds are computed Then each hour bin has distinct lower_bound and upper_bound based on its historical baseline And a spike at 14:00 that exceeds the 14:00 upper_bound is flagged anomaly=true And the same absolute count at 03:00 that is below the 03:00 upper_bound is not flagged And thresholds for one hour bin are not applied to any other hour bin
Continuous Baseline Adaptation After Launch Step-Change
Given a step-change at t0 increases the baseline level for S,T due to a product launch And adaptation_window_hours is configured to 24 When post-launch data is ingested Then anomalies may be emitted during the initial adaptation but for no more than the first K windows where K corresponds to the configured adaptation window And within 24 hours after t0, thresholds re-center so that at least 95% of windows within ±1 standard deviation of the new mean are not flagged as anomalies And thresholds metadata shows last_updated_ts within the most recent update cycle
Threshold Publication and Consumption Contract
Given threshold computation completes for S,T When publishing to the detection service Then a payload including {segment_key, theme_key, lower_bound, upper_bound, scoring_params, effective_from, ttl, version} is sent and acknowledged within 60 seconds And the detection service adopts the new version within the next evaluation window and logs the adopted version And if acknowledgement fails or ttl expires, the publisher retries with exponential backoff and falls back to the last known good thresholds And over any rolling 24 hours, at least 99% of publish attempts succeed and are applied within 2 minutes
Launch & Seasonality Awareness
"As a product manager, I want the system to account for launches and seasonality so that expected spikes don’t trigger noise while true anomalies are still caught early."
Description

Integrates product release calendars, campaigns, and regional holidays to adjust priors and widen or narrow thresholds during expected spikes or dips while preserving sensitivity to out-of-profile changes. Automatically detects recurring seasonality via decomposition and annotates baselines with seasonal components. Provides UI/API to add expected-impact windows and annotations that propagate to detection and reporting.

Acceptance Criteria
External Schedules Ingestion & Segment Mapping
Given release calendars (ICS), marketing campaigns (CSV/API), and regional holidays (public API) are connected And segment mapping rules exist for plan, region, version, channel, and time-of-day When the system ingests updates or new events Then events are normalized with start/end UTC, expected direction (up/down), magnitude (% or low/med/high), and targeted segments And conflicts or overlaps are resolved by precedence rules (holiday > release > campaign) and recorded And successful ingestions are visible in the UI list and via GET /expected-impacts within 60 seconds
Launch Window Threshold Adjustment & Reversion
Given a launch event targeting plan=Pro, version=2.3, channel=web with expected +150% impact from T0 to T1 And a seasonally-adjusted baseline exists for those segments When observed volume rises within T0..T1 by up to the configured expected impact Then anomaly thresholds for affected segments are widened so no alerts are generated solely due to the expected rise And the launch annotation appears on the theme map and in detection detail And after T1, thresholds revert to pre-window values within 15 minutes and normal alerting resumes
Regional Holiday-Aware Baselines
Given a regional holiday configured for region=US with expected -40% impact and no holiday for region=EU When observed volume drops 35-45% in US and remains within +/-10% of baseline in EU during the holiday Then US segments do not generate anomaly alerts for the drop And EU segments continue with unchanged thresholds and alerting And reports and API responses clearly mark US baselines as holiday-adjusted for the window
Automatic Seasonality Decomposition & Baseline Annotation
Given at least 12 weeks of historical data with weekly and intraday patterns per segment When the system trains seasonality models Then it identifies significant seasonal components per segment (e.g., weekly, intraday) And explains variance with R^2 >= 0.6 on backtest or marks the component as weak if below threshold And updates baselines to remove seasonal effects from anomaly scoring And exposes seasonal components and their strength in UI and API for each segment
Out-of-Profile Anomalies Trigger During Expected Spikes
Given a launch window targeting channel=web only When an anomaly occurs in channel=email exceeding the computed anomaly threshold for that segment during the same window Then an anomaly alert is emitted within 5 minutes with reason "outside expected window" And detection confidence >= 0.8 is included And the presence of the launch window does not suppress this alert And if the observed change for targeted segments exceeds expected magnitude by > 30% or is opposite in direction, an alert is emitted
UI and API for Expected-Impact Windows & Reporting Propagation
Given a user creates expected-impact windows via UI and via POST /expected-impacts with required fields (name, segments, start, end, direction, magnitude, source) When saved successfully Then both entries appear in list views and GET /expected-impacts within 30 seconds And propagate to the detection service effective by the next evaluation cycle (< 5 minutes) And annotations appear on the live theme map and in scheduled reports for the window And all create/update/delete actions are captured in an audit log with user, timestamp, and diff
Explainability of Applied Priors & Thresholds
Given any detection result during a period affected by a schedule or seasonality When a user opens detection details or calls GET /detections/{id} Then the UI/API shows the base baseline value, seasonal adjustment, expected-impact adjustment, final threshold, and the observed value And includes the model version and inputs used to compute them And export via CSV/API returns the same values for reproducibility
Anomaly Surfacing in Ranked Queue
"As a triage owner, I want anomalies to appear in the ranked queue with segment details and confidence so that I can act quickly and sync issues with the right context."
Description

Streams detected anomalies with segment context, severity, and confidence into EchoLens’s ranked queue, merging with existing theme clusters. De-duplicates correlated anomalies across adjacent segments and time buckets, aggregates them under affected themes, and updates one-click Jira/Linear sync payloads with segment annotations. Ensures near-real-time propagation to the queue and live theme map.

Acceptance Criteria
Real-Time Anomaly Entry in Ranked Queue
Given an anomaly detection event with id "A1" is emitted by Adaptive Baselines for theme "Checkout Errors" and segment {plan: Pro, region: NA, version: 3.2.1, channel: chat, window: 09:00–09:15}, severity=High, confidence=0.84 When the event is processed by the anomaly surfacing pipeline Then a corresponding ranked queue item exists under the "Checkout Errors" theme within p95<=30s and p99<=60s of event time And the queue item displays the exact segment context (plan, region, version, channel, time window), severity, and confidence values And no separate top-level queue item is created for the same anomaly outside the theme cluster
De-dup Across Adjacent Segments and Time Buckets
Given two anomaly events A2 and A3 for the same theme with overlapping content, adjacent time buckets (±1 x 15m), and adjacent segments per configured rules (e.g., regions US-East and US-West) When both events are processed within 2 minutes Then the ranked queue contains exactly 1 aggregated item for that theme with segments=[US-East, US-West] and time_window=union(A2,A3) And no additional queue items exist for A2 or A3 And the aggregated item exposes dedup_count=2 and a correlation_id shared by A2 and A3
Theme Aggregation Under Affected Themes
Given theme T exists and an anomaly A4 matches T with similarity>=0.75 When A4 is processed Then T.anomaly_count increments by 1 and T.affected_segments includes A4.segment And T.severity becomes max(previous T.severity, A4.severity) And T.confidence is recomputed as a volume-weighted mean including A4 within 1s of processing And T.id remains unchanged and no new theme is created
Jira/Linear One-Click Sync With Segment Annotations
Given one-click sync is configured for Jira/Linear and a ranked queue item Q exists with aggregated segments and fields {theme: "Checkout Errors", severity: Critical, confidence: 0.92} When a user triggers "Sync to Jira" on Q Then exactly one Jira issue is created within 30s with summary including the theme and description containing segment annotations (plan, region, version, channel, time window), severity, confidence, and a deep link to Q And the created issue stores Q.id as an external key for idempotency And when Q later aggregates new segments, the existing Jira issue is updated within 60s to append the new segments, without creating a duplicate issue
Live Theme Map Near-Real-Time Update
Given a theme T receives a new anomaly with severity=High and confidence=0.80 When the anomaly is processed Then the live theme map updates T's node within p95<=30s to reflect the latest severity/volume signal And the node tooltip displays the newest segment context (plan, region, version, channel, time window) And clicking the node opens the corresponding ranked queue entry for T
Idempotency and Late-Arrival Handling
Given the same anomaly event A7 may be delivered multiple times with the same anomaly_id When A7 is processed Then exactly one upsert occurs to the ranked queue/theme, duplicates do not create additional items, and only last_seen is updated Given a late-arriving anomaly event A8 with event_time up to 24h in the past When A8 is processed Then it is deduplicated/aggregated under its theme and reflected in the ranked queue within 120s of arrival with backfilled=true
Explainable Baseline Adjustments
"As a PM, I want clear explanations for baseline and threshold changes so that I can trust alerts and justify actions to stakeholders."
Description

Provides transparent explanations for baseline and threshold changes, including baseline values, thresholds, observed metrics, seasonal components, and any launch or event adjustments applied. Renders “why this alerted” and “why this was suppressed” tooltips in the queue and theme map, and exposes an audit trail with timestamps, data windows, and model versions to support trust and compliance.

Acceptance Criteria
Tooltip: Why This Alerted (Queue and Theme Map)
Given a theme is marked as Alerting in the ranked queue or theme map, When the user hovers or focuses the “why this alerted” icon, Then a tooltip appears within 300 ms and displays: baseline value, threshold, observed metric at alert time, seasonal component contribution (%), launch/event adjustments applied, data window (start–end, UTC), model version, confidence score, and segment keys (plan, region, version, channel, time-of-day). Given the tooltip is open, When the user activates “View audit trail”, Then the corresponding audit trail entry opens and the alert ID, segment keys, and timestamp match the tooltip values. Given network delay, When the explanation data is not yet loaded, Then a skeleton state is shown immediately and the tooltip content loads within 2 s at the 95th percentile or shows a retriable error message.
Tooltip: Why Suppressed (Non-Alerting Items)
Given a theme evaluated but not alerted due to adaptive baseline thresholds, When the user opens the “why this was suppressed” tooltip, Then it displays: baseline value, threshold at evaluation time, observed metric, suppression reason (seasonality, low confidence, launch window adjustment, or minimum volume), seasonal component contribution (%), data window (UTC), and model version. Given suppression reason is seasonality, When the tooltip renders, Then it shows the seasonal component amplitude and proportion of observed metric explained as a percentage. Given the tooltip is open, When the user activates “View audit trail”, Then the corresponding suppression decision record opens with matching identifiers.
Audit Trail: Baseline and Threshold Change Log
Given any baseline or threshold update (automated or user override), When the event occurs, Then an immutable audit entry is written with: ISO-8601 UTC timestamp, segment keys, previous and new baseline values, previous and new thresholds, reason category (seasonality, launch/event, drift, manual override), data window (start–end), model version, algorithm parameters hash, actor (system/user), and a unique record ID. Given an auditor applies filters, When filtering by date range, segment, reason, or model version, Then results return within 1 s at the 90th percentile for up to 1,000 entries and can be exported to CSV. Given permissions are enforced, When a non-admin attempts to export the audit trail, Then the export is blocked and an authorization error is shown.
Change Reason Details and Deltas in Explanations
Given an explanation tooltip is shown (alerted or suppressed), When it renders, Then it includes delta to baseline and delta to threshold (absolute and percentage) and a primary reason label with a rationale text capped at 140 characters. Given a launch/event adjustment influenced the decision, When the explanation renders, Then it shows the event name/ID, adjustment magnitude (absolute and %), effective window, and expiration timestamp, and the linked audit entry references the same event ID. Given no launch/event adjustment exists, When the explanation renders, Then no event fields are shown and the reason label reflects the next most influential factor (e.g., seasonality or drift).
Segment Context and Rollup Transparency
Given an explanation is shown, When the model rolled up due to sparse data, Then the tooltip indicates “Rolled up: Yes”, shows the fallback level used (e.g., removed version or channel), and the widened data window, and the audit trail lists the rollup path. Given sufficient data for the full segment, When no rollup is applied, Then the tooltip lists the exact segment key-values used and indicates “Rolled up: No”. Given a user clicks the segment context in the tooltip, When navigation occurs, Then the theme map highlights the same segment and time window used in the explanation.
Accessibility and Internationalization Compliance
Given a keyboard-only user, When navigating to the info icon, Then the tooltip opens with Enter or Space, closes with Escape, is focus-trapped, and is reachable in the tab order. Given a screen reader user, When the tooltip opens, Then it is announced with appropriate ARIA semantics (tooltip or dialog), each field has a programmatic label, and color contrast meets WCAG 2.1 AA (>= 4.5:1). Given a user locale and timezone are set, When the tooltip and audit trail render, Then dates display in the user’s timezone with offset while records store UTC, and numbers and percentages are formatted per locale.
Analyst Feedback Loop & Overrides
"As a CX lead, I want to correct misfired alerts and set short-term overrides so that future detections better reflect our operational reality."
Description

Enables users to label alerts as true/false positive, snooze or mute segments/themes, and set temporary overrides (e.g., raise threshold for 48 hours). Captures feedback signals to adapt model sensitivity per segment over time, with role-based permissions and safeguards. Provides an API for bulk annotations and integrates feedback into retraining pipelines to continuously reduce noise.

Acceptance Criteria
Mark Alert as True/False Positive
Given an authenticated user with Analyst or Admin role is viewing an alert in the ranked queue When the user selects "Mark as True Positive" or "Mark as False Positive" and confirms Then the alert displays the selected label within 200 ms and the label persists on refresh And a feedback record is stored containing userId, timestamp (UTC), alertId, themeId, segment keys, and decision value And the decision is retrievable via GET /v1/alerts/{alertId} within 5 seconds of submission And the segment-theme feedback counters update atomically and are reflected in analytics within 60 seconds And the user can undo the decision within 24 hours, which removes the feedback record and reverts counters
Snooze or Mute a Segment/Theme
Given a user with Analyst or Admin role selects a segment and/or theme from an alert or the live theme map When the user applies Snooze for a specified duration (15m, 1h, 24h, or custom up to 7d) and provides a reason Then new alerts matching the scope are suppressed from notifications and default queues until the snooze expires, while still being ingested and counted in suppressed metrics And a visible indicator shows "Snoozed until <timestamp> by <user>" on the scoped segment/theme and on affected alerts And snoozes auto-expire at the specified time; mutes persist until explicitly unmuted by an authorized user And suppressed items are accessible in a "Suppressed" view with counts and filters by scope and reason And creating a duplicate snooze for the same scope extends the end time to the latest value instead of creating a new entry And Jira/Linear sync is disabled for suppressed alerts during the suppression window
Temporary Threshold Override
Given an Admin user opens the Adaptive Baselines settings for a specific scope (segment, channel, time-of-day, or region) When the user applies a temporary sensitivity change as a percentage delta (−80% to +200%) or absolute threshold and sets a duration between 1h and 72h Then the anomaly detection uses the override starting in the next processing cycle (under 5 minutes) and an "Override active" badge appears on affected views And the system validates inputs and blocks overrides outside safety bounds with an inline error And on expiry, the system automatically reverts to the learned baseline without manual action And overlapping overrides resolve by most recent wins and all overrides are recorded with before/after values, scope, actor, reason, and expiry
Role-Based Permissions and Safeguards
Given workspace roles of Admin, Analyst, and Viewer are configured When a user attempts to label alerts, snooze/mute scopes, set overrides, or call bulk feedback APIs Then permissions enforce: Admin = all actions; Analyst = label and snooze/mute only; Viewer = read-only; unauthorized actions return 403 with error code RBAC_001 And actions that materially reduce sensitivity (mute > 7 days or sensitivity decrease > 50%) invoke a 2-step confirmation requiring a reason And segments marked "Critical" by Admin cannot be muted or decreased in sensitivity; the UI displays a non-dismissible warning and blocks the action
Bulk Annotations API
Given a service account with token scope feedback:write and a valid idempotency-key When the client POSTs to /v1/feedback/bulk with up to 5,000 items (each containing alertId or themeId+segment keys, decision, reason, timestamp) Then the API validates the payload, rejects malformed items with per-item errors, and accepts valid items for async processing And the endpoint returns 202 with jobId; GET /v1/feedback/jobs/{jobId} returns status and counts for accepted, rejected, and applied items And applying decisions is idempotent for 24 hours based on idempotency-key + itemId, preventing duplicate effects And the system sustains at least 1,000 items/sec applied with p95 latency under 2 seconds for a 10,000-item job And audit entries include API client id, token scope, and source IP for each applied item
Feedback Integrated into Sensitivity Model
Given a segment-theme has at least 30 labeled alerts and at least 10 false-positive labels in the last 14 days When the nightly retraining pipeline runs successfully Then the segment-level sensitivity updates are applied and recorded as a new model version with timestamp and changelog entry And in the subsequent 7-day evaluation window, false positives decrease by ≥20% without increasing missed true positives by >5% on a holdout set; if constraints are violated the update auto-rolls back And the UI displays "Updated from feedback" with the model version and an option to rollback; rollback takes effect within 5 minutes and restores prior metrics
Audit Logging and Revert Changes
Given audit logging is enabled for the workspace When any user performs a feedback action (label, snooze, mute, override, bulk API) Then an immutable audit record is created containing actor, role, action, scope, previous value, new value, reason, timestamp (UTC), requestId, and IP And audit records are filterable and exportable to CSV for a selectable date range and scope And Admins can revert a single action from the audit detail; the system creates a compensating action and updates state consistently across UI, APIs, and model inputs within 60 seconds
Sparse Segment Backoff & Data Quality Guardrails
"As a product manager, I want the system to avoid alerting on thin or poor-quality data so that we reduce noise and maintain trust in the signal."
Description

Monitors segment volume, missingness, and volatility to detect sparse or low-quality data. Automatically backs off from granular segments to parent segments (e.g., version → product, region → global) when learning signals are insufficient, and flags backoffs in outputs. Applies minimum sample sizes, outlier clipping, and delay-aware handling to prevent premature or spurious alerts. Publishes data quality indicators to the UI and downstream services.

Acceptance Criteria
Backoff on Minimum Sample Size Breach
Given config min_sample_size_per_window=50 and window=24h And a segment (plan=Pro, region=US, version=1.2.3, channel=email, hour=10) has sample_count=27 within the window When baselines and anomaly thresholds are computed for that segment Then the system does not compute a granular baseline for the segment And the system backs off to the parent segment defined by the hierarchy (version→product) And no anomaly alert is emitted at the granular level for the suppressed segment within the window And the backoff decision records sample_count=27 and min_sample_size=50 in the data_quality indicators
Delay-Aware Alert Suppression for Ingestion Lag
Given config ingest_delay_grace=15m And the latest_event_timestamp for a segment is 32m behind current_time When the anomaly evaluation job runs for that segment Then the system suppresses alerts and defers baseline updates for that segment And the system sets data_quality.ingestion_lag_seconds=1920 and data_quality.data_delayed=true And a backoff/suppression reason code includes "ingestion_delay_exceeded" And once ingestion_lag_seconds ≤ 900 for two consecutive runs, evaluation resumes without emitting retroactive alerts for the delayed window
Outlier Clipping Prior to Baseline Learning
Given config outlier_clip_percentiles=(1,99) And within the evaluation window the segment time series contains extreme points beyond the 99th percentile and below the 1st percentile When computing baselines, volatility, and anomaly thresholds Then values outside the percentile bounds are clipped to the 1st/99th percentile limits before calculations And anomaly detection uses the clipped series, not the raw extremes And the system publishes data_quality.outlier_rate as the fraction of points clipped within the window And no anomaly alert is emitted that is solely attributable to a single clipped outlier
Hierarchical Backoff Order and Stabilization
Given a backoff hierarchy: version→product, region→global, channel→all, plan→all, hour→day And config min_sample_size_per_window=50, missingness_max_percent=10, volatility_max_cv=1.2, stabilization_min_duration=30m And a granular segment fails any quality check (sample_count<50 or missingness_percent>10 or volatility_cv>1.2) When computing baselines and thresholds Then the system backs off to the nearest parent that satisfies all quality checks within the same window And if no parent satisfies checks, the system uses the global aggregate and marks quality_status="degraded" And the selected backoff level remains stable for at least 30 minutes unless the parent subsequently fails its checks And the backoff decision records reason codes (e.g., "low_sample_size", "high_missingness", "high_volatility") with measured values
Backoff Reason Flagging in UI and API
Given an anomaly or baseline summary is produced using a parent segment due to backoff When the result is exposed via API and UI Then the API payload includes backed_off=true, from_segment, to_segment, backoff_reasons[], sample_count, min_sample_size, missingness_percent, volatility_cv, outlier_rate And the UI surfaces a "Backed off" badge on the item with a tooltip showing from_segment→to_segment and backoff_reasons with metrics And downstream Jira/Linear sync annotations include a note: "Backed off from <from_segment> to <to_segment> due to <reasons>"
Data Quality Indicators Published to UI and Downstream
Given baselines are computed (with or without backoff) for any segment When exposing results to consumers Then the UI shows a Data Quality panel per segment with: sample_count, missingness_percent, volatility_cv, outlier_rate, ingestion_lag_seconds, last_event_timestamp, baseline_window_start/end And the public API endpoint for segment baseline includes a data_quality object with the same fields and a quality_status enum {ok, delayed, sparse, volatile, degraded} And downstream event messages (e.g., theme_score_updated) include data_quality and backed_off metadata for traceability

Interactive Alerts

Delivers Slack pings with severity, top example quotes, and one-tap actions—assign owner, open Jira/Linear, acknowledge, snooze, or tweak sensitivity. Speeds response by letting teams triage and route issues without leaving chat.

Requirements

Slack Workspace OAuth & App Install
"As a Slack workspace admin, I want to install and authorize the EchoLens Slack app so that my team can securely receive and act on alerts in our channels."
Description

Implements a first‑party Slack app with OAuth v2 for secure workspace installation, capturing and encrypting bot/user tokens, and supporting multiple Slack workspaces per EchoLens organization. Defines and requests least‑privilege scopes (e.g., chat:write, commands, channels:read, users:read) and persists installation metadata (team, enterprise grid, installer, scopes, token expiry/rotation). Maps Slack users to EchoLens users via email or ID for permission checks on actions. Provides uninstall handling, health checks, and a secure callback endpoint with state/PKCE, CSRF protection, and rate‑limit backoff. Exposes admin UI in EchoLens for managing installations, channels, and default alert destinations.

Acceptance Criteria
Secure Slack OAuth and Callback Validation
Given an EchoLens org admin initiates Slack app install from EchoLens And a unique state value and PKCE code challenge are generated and stored server-side When Slack redirects back to the EchoLens OAuth callback with a valid code and matching state Then the server exchanges the code for tokens using PKCE And only the predefined scopes are requested and granted And the user is redirected to a success screen within 5 seconds And the Slack team_id, enterprise_id (if any), installer Slack user id, and bot_user_id are captured and persisted Given the state does not match or the code verifier fails When the callback is processed Then the install is rejected with HTTP 400 and no tokens are stored Given a request is received at any Slack interactive/event callback endpoint When the Slack signing secret signature and timestamp are validated Then requests with invalid signatures or clock skew greater than 5 minutes are rejected with HTTP 401 and are not processed Given the installer cancels or denies scopes When Slack redirects with an error Then EchoLens shows a failure message and no installation record is created
Least-Privilege Scope Set Enforcement
Given the Slack app is installed via OAuth When scopes are requested Then the scope set equals exactly ["chat:write","commands","channels:read","users:read"] by default And no additional scopes are requested without explicit admin opt-in in EchoLens Given Slack returns a granted scope set When the installation is saved Then the stored scopes exactly match the granted set And any attempt to call a Slack API outside granted scopes is blocked and logged with reason "insufficient_scope"
Multiple Slack Workspaces per Organization
Given an EchoLens organization with an existing Slack installation When a second Slack workspace completes OAuth Then a separate installation record is created with a unique team_id and tokens And actions originating from each workspace use the correct workspace tokens And posting or channel discovery never crosses between different team_ids Given two installations exist When sending a test alert to a selected default channel Then the message is delivered only to the channel in the selected workspace
Installation Metadata Persistence and Uninstall Handling
Given a successful installation When the installation is saved Then the following fields are persisted: team_id, enterprise_id (nullable), installer_slack_user_id, installer_email (if provided), bot_user_id, scope_list, installed_at timestamp, token_expires_at (if rotating), token_rotation_enabled flag, last_health_check_at (nullable) And retrieving the installation via API returns these fields accurately Given an existing installation is re-installed by any org admin When OAuth completes Then the record is upserted, updating scopes and tokens while preserving team_id and installation history Given the Slack app is uninstalled from a workspace When the uninstall event/callback is received Then the installation is marked deactivated within 60 seconds, tokens are revoked, future postings are prevented, and the admin UI reflects the deactivated status
Token Encryption, Rotation, and Rate-Limit Backoff
Given tokens are obtained from Slack When storing tokens Then access and refresh tokens are encrypted at rest using AES-256-GCM with keys managed by KMS And tokens are never logged or shown in plaintext in UI or logs Given a token has expired When an API call is made Then the system uses the refresh token to obtain a new access token And retries the original call once And on failure after 3 attempts or a non-retryable error, the installation is marked "unhealthy" and an admin alert is generated Given Slack returns HTTP 429 with a Retry-After header When performing Slack API calls Then the client honors Retry-After and applies exponential backoff up to 3 retries And no more than 1 request per second is sent to the same method while backoff is active And rate-limit metrics are recorded
Slack User Mapping and Permission Checks
Given a Slack interactive action is received with user.id and (if available) user.email When mapping to EchoLens Then the system first maps by previously linked Slack user ID Else maps by verified email to an active EchoLens user in the same org And if no match is found, the action responds with a private Slack message instructing the user to link their account Given a mapped EchoLens user lacks the required role for the requested action (e.g., assign owner, open Jira/Linear, tweak sensitivity) When the action is attempted Then the action is denied And a private Slack message explains the required permission And an audit entry records the action, Slack user, mapped EchoLens user, and outcome
Admin UI for Installations, Channels, and Default Destinations
Given an org admin opens EchoLens > Admin > Slack When viewing installations Then a table lists each workspace with team name, team_id, health status, scopes, and installed_at And the admin can disconnect, run a health check, and send a test message per installation Given an installation is selected When configuring channels Then the UI lists available public channels fetched via channels:read And the admin can set and save a default alert destination channel And saving persists the channel id and name and is immediately used by Interactive Alerts Given a non-admin user opens the same page When attempting to modify settings Then controls are disabled and a notice indicates admin-only access
Alert Message Formatting & Threading
"As a PM or CX lead, I want alerts that clearly show severity and top quotes in a compact, threaded message so that I can grasp impact quickly without leaving Slack."
Description

Delivers alerts to Slack using Block Kit with clear severity badges, theme/title, confidence score, top example quotes (redacted/PII‑safe), and quick links back to the EchoLens theme map. Posts to configured channels or DMs and starts/continues a dedicated thread per theme/incident to keep discussion contained. Supports message edits to reflect state changes (acknowledged, snoozed, assigned) and adds ephemeral hints for context. Provides accessible alt text and plaintext fallbacks. Ensures localization, timezone‑aware timestamps, and graceful degradation if blocks exceed Slack limits.

Acceptance Criteria
Slack Alert Formatting: Severity, Title, Confidence, Quotes (PII-safe), and Theme Map Link
Given an EchoLens theme triggers an alert with a defined severity and confidence score When the alert is sent to Slack Then the message is posted using Block Kit with a visible color-coded severity badge, the theme/title as the header, and the confidence score displayed as a percentage with one decimal And then up to 3 top example quotes are included and all emails, phone numbers, and credit card-like numbers are replaced with [REDACTED] And then each quote shows its source type and source timestamp And then a link or button to the EchoLens theme map is included and the URL contains the theme_id and source context parameters And then a plaintext fallback includes severity, title, confidence, and the first quote And then the alert posts to the configured Slack destination (channel or DM) as specified by the routing rules
Threading Per Theme/Incident in Slack
Given multiple alerts or updates relate to the same theme or incident When an alert is sent to Slack Then the first alert creates a root message and subsequent alerts for the same theme/incident are posted as replies in the same thread identified by thread_ts And then alerts for different themes or incidents create distinct root messages and threads And then if the original thread is unavailable (deleted or cannot be found), a new root message is created and subsequent messages for that theme/incident continue in the new thread And then thread replies do not notify the entire channel by default (no broadcast)
Message Edits Reflect State Changes (Acknowledged, Snoozed, Assigned)
Given a user acknowledges, snoozes, or assigns an alert from EchoLens or Slack When the state change is applied Then the root Slack message is edited within 5 seconds to show the current state badge and assignee if applicable And then a system reply is added in the thread summarizing the action, actor, timestamp, and any provided reason And then prior state indicators are replaced rather than duplicated And then reversing a snooze or reassigning updates the message and thread accordingly
Ephemeral Context Hints in Slack
Given a user opens the alert actions or initiates an operation that requires context in Slack When context is needed for that user Then an ephemeral message is sent visible only to that user with a short permissions summary, next steps, and a link to documentation And then no other channel members can view the ephemeral hint And then the hint is not persisted in the thread and expires per Slack behavior
Localization and Timezone-Aware Timestamps
Given the workspace default locale and timezone and any user-specific overrides in Slack When an alert message and subsequent thread updates are posted Then static labels such as Severity, Confidence, and Assigned are localized to the viewer's locale if available, else the workspace default, else English And then timestamps render in the viewer's timezone using Slack date formatting and are accurate to within 1 minute of server time And then numeric values including percentages use locale-appropriate formatting
Graceful Degradation on Slack Block Kit Limits
Given the composed alert would exceed Slack Block Kit limits due to block count, text length, or field limits When composing the Slack message Then the system truncates quotes to the configured maximum and switches to a compact layout preserving severity, title, confidence, first quote, and the theme map link And then if the compact layout would still exceed limits, a plaintext-only summary is posted stating Content truncated—open in EchoLens and includes the theme map link And then the post succeeds without Slack API error and a telemetry event is recorded with reason=limit_fallback and the message identifier
Accessibility: Alt Text and Screen Reader Support
Given the alert contains non-text elements such as images, emojis, or buttons When the message is posted to Slack Then every image element includes descriptive alt_text and every interactive element has an accessible label And then the top-level message contains a plaintext fallback that conveys severity, title, confidence, and the first quote for clients without Block Kit And then common screen readers read the severity, title, confidence, and first quote in a logical order when navigating the message
Interactive One‑Tap Actions
"As a triager, I want one‑tap actions to assign, create tickets, acknowledge, and snooze alerts so that I can route and de‑risk issues instantly without leaving Slack."
Description

Adds interactive buttons and menus to Slack alerts enabling assign owner, open Jira/Linear ticket, acknowledge, and snooze with preset durations. Handles Slack interactivity via signed request verification, idempotent action endpoints, and optimistic UI updates. Enforces permissions and ownership rules, and writes back state to EchoLens (and threads) in real time. Creates Jira/Linear issues via existing integrations, attaches source quotes/links, and posts the created ticket link back to the thread. Includes error handling with retries and user‑visible failure messages, plus audit logs for who did what and when.

Acceptance Criteria
Assign Owner from Slack Alert
- Given a Slack alert message with an "Assign owner" action and the actor has Assign permission, When the actor selects a teammate from the assign menu and confirms, Then EchoLens sets the alert owner to the selected teammate, the Slack thread root updates within 2 seconds with "Owner: @teammate (set by @actor)", and EchoLens alert detail reflects the same owner. - Given transient retries cause the action to be invoked twice with the same idempotency key, When the assign action is processed, Then only one owner change occurs and duplicates return 200 with the same response body (idempotent). - Given the actor lacks Assign permission or overwrite is disallowed by policy, When the actor attempts to assign, Then an ephemeral error explains the restriction and no state changes are persisted. - Given the assignment succeeds, Then an audit entry is recorded with actor, target owner, alertId, timestamp (UTC), and Slack message ID.
Create Jira/Linear Issue from Slack Alert
- Given EchoLens is connected to Jira or Linear and the Slack alert has an "Open Ticket" action, When the actor chooses Jira or Linear and confirms, Then a ticket is created with title from theme name, description including top 3 example quotes and source links, and the Slack thread updates within 5 seconds with the created ticket ID and URL. - Given the "Open Ticket" action is retried or clicked twice within 24 hours with the same alert and destination, When processed, Then exactly one external ticket exists and duplicates return 200 with the original ticket link (idempotent). - Given the integration is missing or credentials are invalid, When the actor attempts to create a ticket, Then an ephemeral error describes the issue and offers a "Connect" link; no ticket is created and the thread is not modified. - Given a transient 429/5xx from the destination API, When creating a ticket, Then the system retries up to 3 times with exponential backoff (total < 10 seconds) before failing with a user-visible error containing a correlation ID.
Acknowledge Alert with Real-Time State Sync
- Given a Slack alert with an "Acknowledge" button and the actor has Ack permission, When the actor taps Acknowledge, Then the alert state in EchoLens is set to acknowledged with actor and timestamp (UTC), the Slack thread root updates within 2 seconds to show "Acknowledged by @actor", and the button toggles to "Unacknowledge". - Given the actor taps Acknowledge multiple times or Slack delivers duplicates, When processed, Then subsequent calls are no-ops returning 200 with the current state (idempotent). - Given the actor lacks Ack permission, When attempting to acknowledge, Then the actor receives an ephemeral error and the thread and EchoLens state remain unchanged.
Snooze Alert with Preset Durations
- Given a Slack alert with a "Snooze" menu offering 1h, 4h, and 24h options and the actor has Snooze permission, When the actor selects a duration, Then the alert is snoozed until the expected timestamp (UTC), further Slack pings for that alert are suppressed during the window, and the thread root shows "Snoozed until <time> by @actor" with an "Unsnooze" action. - Given the snooze period expires, When the time is reached, Then the snooze state clears automatically and normal alerting resumes. - Given the actor selects Unsnooze before expiry, Then the snooze state clears immediately, the thread badge is removed, and future pings resume. - Given duplicate Snooze requests with the same idempotency key, When processed, Then only one snooze state is recorded and duplicates return the same response.
Permissions and Ownership Rules Enforcement
- Given Slack user identity is mapped to an EchoLens user and role, When the user attempts Assign/Acknowledge/Snooze/Open Ticket actions, Then permissions are enforced per role policy: only authorized roles can perform each action; unauthorized attempts return ephemeral 403-style messages and no changes occur. - Given the Slack user is unmapped to an EchoLens user, When any action is attempted, Then the user is prompted via ephemeral message to link their account via SSO and the action is not executed. - Given an alert is owned by Team A and cross-team assignment is disabled, When a Team B user attempts to assign, Then the action is blocked with an explanatory message and no state change occurs.
Slack Signed Request Verification and Replay Protection
- Given an incoming Slack interactivity request, When the X-Slack-Signature and X-Slack-Request-Timestamp are validated per Slack v2 scheme, Then requests with invalid signatures or timestamps older than 5 minutes are rejected with 401 and not processed. - Given a valid request is received more than once (replay or retry) within 10 minutes, When processed, Then the server detects replay via idempotency key or request hash and returns 200 with the original result without performing the side effect again. - Given signature verification passes, When handling the action, Then the handler responds within 3 seconds or immediately acknowledges and completes asynchronously to avoid Slack timeouts.
Audit Logging for Slack Actions
- Given any action (Assign, Acknowledge, Snooze, Open Ticket) succeeds or fails, When it is processed, Then an append-only audit entry is recorded including actor Slack ID and mapped EchoLens user ID, action type, alertId, parameters, result (success/failure), correlation/idempotency key, timestamp (UTC), and any external resource IDs. - Given an authorized EchoLens admin views the alert’s Activity tab, When they filter for "Slack actions", Then they see the corresponding audit entries within 2 seconds of the action with accurate data and can export the entries as CSV. - Given a compliance export job runs via API, When invoked for a date range, Then it returns audit entries signed with a checksum and marked immutable.
In‑Slack Sensitivity Controls & Preferences
"As a product manager, I want to adjust alert sensitivity from within Slack so that I can control noise in context without switching tools."
Description

Enables users to tweak alert sensitivity and noise thresholds directly from Slack via overflow menu, shortcuts, or slash commands. Supports per‑channel and per‑user settings (severity minimums, signal confidence, product areas), with previews showing expected alert volume impact. Persists preferences to EchoLens, syncing with web settings for consistency. Provides quick revert to defaults and temporary quiet hours. Validates inputs, confirms changes in‑thread, and logs adjustments for auditability.

Acceptance Criteria
Adjust Sensitivity via Alert Overflow Menu
Given a Slack Interactive Alert from EchoLens is visible in a channel and the user has permission to manage alert settings for that channel When the user opens the message overflow menu and selects Tweak Sensitivity And the user changes severity minimum, signal confidence threshold, and product area filters And clicks Save Then inputs are validated with inline errors for invalid values (e.g., severity not in {Low, Medium, High, Critical}, confidence not between 0 and 1, unknown product areas) And a threaded confirmation is posted showing actor, scope (channel), new values, and a link to View in EchoLens And the new settings take effect for subsequent alerts in that channel within 10 seconds And the settings persist to EchoLens and are visible in the web Settings > Alerts page within 60 seconds
Per-Channel vs Per-User Preference Scoping and Precedence
Given a channel has channel-level settings (e.g., min severity=High) And a user in that channel has per-user overrides (e.g., min severity=Critical) When an alert with severity=High is emitted Then the alert is posted to the channel (meets channel rule) And the user does not receive a personal DM/mention notification from EchoLens (does not meet user override) And removing the user override results in the user receiving the next qualifying personal notification within 60 seconds of the change And per-user overrides affect only that user's personal notifications and do not change which alerts post to the channel
Slash Command and Shortcut Configuration with Validation
Given the user enters /echolens sensitivity set severity=High confidence=0.8 areas=Billing,Auth scope=channel in a channel where the app is present When the command is submitted Then the app responds ephemerally within 2 seconds with a parsed summary and a Preview button And clicking Preview shows the expected alert volume impact panel for the specified scope And clicking Save applies the settings and posts a thread confirmation And invalid inputs (e.g., severity=Urgent, confidence=1.5, unknown area) return an ephemeral error with corrected examples And users without permission receive an ephemeral error explaining required permission And invoking the EchoLens: Tweak Sensitivity shortcut opens a modal pre-filled with current settings for the channel or user context and follows the same validation and save flow
Preview of Expected Alert Volume Impact Before Saving
Given the user has modified sensitivity values but not yet saved When the Preview action is requested Then the preview is computed using the last 7 days of alert data for the selected scope and returns within 5 seconds And it displays baseline daily average, projected daily average, and percent change And it lists the top 3 themes most affected by the change And if sample size < 20 alerts, the preview displays a Low data notice And no settings are persisted until Save is clicked
Temporary Quiet Hours (Per-Channel and Per-User)
Given the user sets Quiet Hours 22:00–07:00 local time for channel scope via Slack When quiet hours begin Then EchoLens suppresses posting channel alerts during the window and logs suppressed counts And outside the window, alerts post normally with no backlog replay And a Quiet Hours badge is shown in the confirmation and visible in web settings for that scope And the user can override with Acknowledge and Post Once to post a single alert while keeping quiet hours active And quiet hours can also be set for a fixed duration (e.g., /echolens snooze 2h) and expire automatically at the correct local time
Revert to Defaults and Undo Confirmation
Given non-default sensitivity settings are active for a scope When the user selects Revert to Defaults and confirms Then settings reset to workspace/product defaults within 10 seconds and a thread confirmation shows before/after values And an Undo option is available for 60 seconds; selecting Undo restores the prior settings and posts confirmation And after the undo window, the change is final and recorded in the audit log
Audit Logging and Web Settings Sync
Given any sensitivity or preference change is saved from Slack When the change is committed Then EchoLens records an immutable audit entry with actor Slack user ID, workspace ID, scope (channel/user), before and after values, timestamp (UTC), and request ID And the audit entry is viewable in EchoLens > Audit within 60 seconds and exportable as CSV And duplicate retries are idempotent by request ID (no duplicate entries) And Slack-sourced changes appear in the web settings UI within 60 seconds; web UI changes made later are reflected in Slack modals within 60 seconds
Routing & Escalation Rules Engine
"As a team lead, I want alerts automatically routed and escalated to the right people and channels so that critical issues are acted on promptly."
Description

Implements rules that route alerts to the right Slack channels or owners based on severity, product area, customer tier, tags, or source. Supports on‑call schedules, business hours, and escalation if an alert is not acknowledged within defined SLAs (e.g., ping owner, escalate to team channel, then leadership). Provides a configuration UI with rule testing/simulation and ensures loops/duplicates are prevented. Integrates with EchoLens theme metadata and user directory for accurate targeting.

Acceptance Criteria
Severity and Product Area Routing
Given an incoming alert with severity = Critical and product_area = "Billing" And a rule exists that routes Critical Billing alerts to Slack channel #billing-sev and assigns owner @pm-billing When the alert is generated by EchoLens Then the alert is delivered to #billing-sev within 5 seconds And the owner field on the alert is set to @pm-billing And the rule evaluation log records the matched rule ID, conditions met, and target(s) selected
Customer Tier and Source-Based Targeting
Given an alert with customer_tier = Enterprise and source = "Zendesk" And a rule exists that routes Enterprise Zendesk alerts to #vip-support and assigns @cx-lead When the alert is processed Then the Slack message is posted to #vip-support and the owner is @cx-lead And a non-Enterprise alert with the same theme and source follows its default route and does not post to #vip-support And if no rule matches, the alert is sent to the global fallback channel configured in settings
On-Call and Business Hours Routing
Given business hours are configured as Mon–Fri 09:00–18:00 in timezone America/Los_Angeles And an on-call schedule "core-oncall" is connected to the user directory When a High severity alert arrives at 22:15 local time Then the alert routes to the current primary on-call user from "core-oncall" via Slack DM And the Slack message is not posted to the daytime team channel When the same rule is evaluated at 10:00 local time on a weekday Then the alert routes to the daytime owner/channel as configured and not to on-call And daylight saving transitions use timezone rules to compute business hours correctly
SLA-Based Escalation Ladder
Given an escalation policy with levels L1=owner ping at T+5m, L2=team channel at T+10m, L3=leadership channel at T+20m And the alert is initially unacknowledged When the alert is not acknowledged by T+5m Then a reminder DM is sent to the owner and the escalation log records L1 fired once When the alert remains unacknowledged at T+10m Then a message is posted to the team channel and the escalation log records L2 fired once When the alert remains unacknowledged at T+20m Then a message is posted to the leadership channel and the escalation log records L3 fired once And if the alert is acknowledged at any time, all pending escalations are cancelled and no further levels fire And if the alert is snoozed, SLA timers pause for the snooze duration and resume afterward without skipping levels
Duplicate and Loop Prevention
Given multiple rules could target the same channel for a single alert When the rules are evaluated Then only the highest-priority matching rule is applied per target channel and the alert is posted at most once to that channel And repost suppression prevents duplicate posts for the same alert to the same channel within 30 minutes unless alert state changes And loop detection prevents a rule action from re-triggering its own condition path; evaluation halts with a logged warning when a loop is detected
Rule Configuration UI with Test/Simulation
Given an admin opens the Routing & Escalation Rules UI When creating a rule with conditions across severity, product_area, customer_tier, tags, and source Then the UI validates required fields, unique rule name, and displays computed priority without errors When the admin runs a simulation with a sample payload Then the simulator shows matched rules, chosen targets, owner resolution, escalation ladder preview, and any conflicts/loops detected And simulator runs do not send any Slack messages or create tickets And the UI prevents saving if circular escalations or invalid schedules are detected, showing actionable validation messages
User Directory and Theme Metadata Integration
Given a rule assigns owner by group "Billing PMs" from the user directory When an alert with theme.product_area = "Billing" and customer_tier = Enterprise arrives Then the group resolves to the current responsible user(s) and assigns the primary responsible user as owner And if the group is empty or resolution fails, the rule falls back to the configured team channel without error And rule conditions can reference EchoLens theme metadata fields (e.g., theme_id, product_area, tags) and evaluate them correctly
De‑duplication, Batching, and Rate Limiting
"As a user, I want duplicate or bursty alerts to be deduped and summarized so that channels stay readable while still surfacing critical signals."
Description

Reduces noise by detecting and suppressing near‑duplicate alerts at the theme/cluster level, batching bursts into periodic summaries, and applying per‑channel/user rate limits. Provides cooldown windows and a digest mode with roll‑ups of counts, severity changes, and new exemplar quotes. Ensures critical severity can bypass limits. Exposes controls in settings and indicates in Slack when messages are summarized or suppressed, with a link to full detail in EchoLens.

Acceptance Criteria
Cluster-Level De-duplication with Confidence Threshold
Given de-duplication is enabled with a similarity threshold of 0.85 at the theme/cluster level When 10 alerts from the same cluster arrive within 15 minutes and each has similarity >= 0.85 to the cluster centroid Then only the first alert is sent to Slack and the subsequent 9 are suppressed and grouped under the first alert in EchoLens And the suppression group displays suppressed_count=9 and the latest_received timestamp And the cluster view shows the top 3 exemplar quotes from unique conversations
Burst Batching into Periodic Summaries
Given batching is enabled with a 5-minute window and max summary size of 50 alerts When more than one alert for a cluster arrives during the window Then a single summary message is posted to Slack at window end with total alert count, distinct source counts (reviews, emails, tickets, chats), and net severity change for the window And if alerts exceed 50 during a window, multiple summaries are posted back-to-back with each capped at 50 without exceeding Slack API rate limits And the summary includes the top 2 exemplar quotes and a "View all" deep link to the cluster in EchoLens
Per-Channel and Per-User Rate Limiting with Cooldown and Overrides
Given a Slack channel rate limit of 10 alerts per 10 minutes and a user DM rate limit of 3 alerts per 10 minutes with a 15-minute cooldown When either limit is reached Then additional alerts are not delivered during the cooldown and are queued for the next batch or digest And a single notification is posted indicating the rate limit reached and the remaining cooldown time And users with the "Alert Admin" role can invoke a one-tap "Temporarily lift limit (30m)" action that immediately resumes deliveries and is recorded in the audit log And limits are tracked and enforced independently per channel and per user
Critical Severity Bypass of Limits
Given critical severity is configured to bypass de-duplication, batching, and rate limits When a critical-severity alert arrives during an active cooldown, suppression, or batching window Then the alert is delivered immediately to Slack with a "Bypassed limits" indicator And the delivery is logged with reason=critical_bypass in the EchoLens audit log
Digest Mode Roll-ups with Counts, Severity Changes, and New Exemplars
Given digest mode is enabled daily for 9:00 AM local time using the Slack channel's timezone When 24 hours have elapsed since the last digest Then a digest message is posted summarizing, per cluster: total alert count, delta since previous digest, current severity, severity change direction, and suppressed alert count And the digest includes up to 3 new exemplar quotes not previously surfaced, with source labels and timestamps And the digest contains deep links to each cluster in EchoLens and a control to adjust sensitivity
Slack Indicators for Summarized or Suppressed Alerts with Link to EchoLens
Given alerts are summarized or suppressed by the system When a summary is posted or a suppression occurs Then the Slack message shows a "Summarized" or "Suppressed" badge, the counts (suppressed N, summarized M), and a "View details" deep link to EchoLens And suppressed alerts do not create separate Slack messages; instead, the latest related message's footer updates with the new suppressed count within 10 seconds
Settings Controls for De-duplication, Batching, Rate Limits, and Bypass
Given an admin opens EchoLens Settings > Interactive Alerts > Noise Controls When they view and modify controls Then they can configure: de-dup similarity threshold (0.50–0.95), batching interval (1–15 minutes), per-channel and per-user rate limits (1–100 per 10 minutes), cooldown duration (5–60 minutes), digest schedule (cron or presets), and a critical-bypass toggle And changes require clicking Save, take effect within 60 seconds, display a success toast, and are recorded in the audit log with actor, before/after values, and timestamp And only Admin or Owner roles can save changes, and the last 10 versions are retained with one-click rollback
Audit Trail & Delivery Metrics
"As a compliance owner, I want audit logs and delivery metrics for Slack alerts so that we can analyze responsiveness and meet governance requirements."
Description

Captures a complete audit trail of alert deliveries and actions (who acknowledged, assigned, created tickets, changed sensitivity) with timestamps and before/after states. Tracks delivery metrics such as sent, delivered, failed, and first response time per alert/channel/user. Provides export APIs and a lightweight dashboard in EchoLens to analyze responsiveness, identify noisy rules, and prove compliance. Integrates with existing logging/observability and respects data retention policies.

Acceptance Criteria
Record Alert Actions with Before/After and Timestamps
Given an alert exists and a user performs an action (acknowledge, assign owner, create Jira/Linear ticket, change sensitivity) via Slack or EchoLens web When the action is submitted Then an audit event is persisted within 2 seconds (p95) containing alert_id, action_type, actor_id, actor_source, action_id (UUIDv4), correlation_id, ISO-8601 UTC timestamp with ms, before_state, and after_state limited to changed fields And the audit event appears in the alert’s audit trail ordered by timestamp ascending And attempts by unauthorized users are rejected with 403 and no audit event is created except a security_denied event with reason And duplicate actions with the same correlation_id within 2 minutes are de-duplicated and only one audit event is stored
Delivery Status Tracking per Channel and User
Given an alert is sent to Slack destinations (channels, DMs) When a send attempt occurs Then a delivery record is created with alert_id, destination_id, destination_type, user_id (if DM), attempt_number, request_id, ISO-8601 UTC timestamp, and status=sent When Slack API responds success Then the record is updated to status=delivered and delivery_timestamp is set When Slack API responds error or times out Then the record is updated to status=failed with error_code and error_message, and retry policy is applied and logged as subsequent attempts And roll-up metrics per alert, per destination, and per user are computed: sent, delivered, failed, delivery_rate=(delivered/sent)
First Response Time Measurement
Given an alert has at least one delivered record When the first qualifying action (acknowledge OR assign owner OR create Jira/Linear ticket) is recorded in the audit trail Then first_response_time_ms is computed as action_timestamp - earliest_delivery_timestamp across that alert/destination scope And FRT is stored at levels: per alert overall, per destination, and per user where applicable And if no qualifying action occurs within the observation window (default 7 days), FRT is null and status is marked no_response
Export API for Audit and Delivery Metrics
Given valid API credentials with export:read scope When a client calls GET /v1/exports/audit with filters (date_range, alert_id, action_type, actor_id, destination_id, status) and pagination (limit<=10000, cursor) Then the API returns 200 within 2 seconds (p95) with JSONL or JSON array including all requested fields and next_cursor when more data exists And PII fields follow tenant redaction rules and only allowed fields are exported And calling GET /v1/exports/metrics with filters (date_range, group_by in [alert, destination, user, rule], rollups in [sent, delivered, failed, delivery_rate, first_response_time_ms]) returns aggregated rows with accurate counts and computed rates And 401 is returned for invalid token, 403 for missing scope, 429 for rate limit with Retry-After header
Dashboard for Responsiveness and Noisy Rules
Given a user opens the Audit & Delivery Metrics dashboard with a time range filter When data loads Then tiles display: Avg FRT, P50/P90 FRT, Sent, Delivered, Failed, Delivery Rate, Action Rate, per selected scope (team, channel, user) And a table lists top rules by alert volume with columns: rule_id, alerts, actions, action_rate, failure_rate, and a badge for noisy if action_rate<10% and alerts>100 in range And clicking a row drills to alert list with filters applied and links to raw audit trail And all visualizations refresh under 3 seconds (p95) for 90-day ranges
Observability and Logging Integration
Given a tenant configures an external sink (e.g., Datadog, Splunk) with credentials When audit or delivery events are produced Then structured logs (JSON) are forwarded at-least-once with fields: tenant_id, alert_id, event_type, severity, timestamps, and correlation_id And if the sink is unavailable, events are queued durably for 24 hours and retried with exponential backoff; after TTL, dropped events are counted and exposed as a metric and in an admin report And log forwarding can be toggled per tenant without impacting internal storage And throughput of 500 events/sec is sustained with <5% forwarding latency >5 seconds
Data Retention and Purge Compliance
Given a tenant sets retention_days (default 90) When the nightly purge job runs Then audit and delivery records older than retention_days are permanently deleted from primary and replica stores within 24 hours And exports and dashboards exclude purged records consistently And a purge_summary event is written containing tenant_id, cutoff_date, records_deleted, and job_status And tenants with legal_hold=true are excluded from purge until the flag is removed

Root-Cause Hints

Correlates spikes with recent deploys, feature flags, app versions, and incident logs to propose likely drivers. Shaves investigation time by pointing PMs and CX leads to where to look first.

Requirements

CI/CD Deploy Webhooks Integration
"As a PM/CX lead, I want EchoLens to ingest deploy events with service, environment, and timestamps so that spikes can be correlated to specific releases."
Description

Add first-class ingestion of deploy events from common CI/CD providers (GitHub Actions, GitLab CI, CircleCI, Jenkins) via signed webhooks and REST backfill. Normalize payloads to capture service/repo, environment, version/commit SHA, artifact, region, and start/end timestamps with idempotency keys. Store into a time-indexed timeline aligned to EchoLens theme spikes to enable correlation. Provide setup UI with verification, secret rotation, retry/queue handling, and per-environment mapping. Benefits include reliable visibility into what shipped when, forming a primary signal for root-cause hinting.

Acceptance Criteria
Signed Webhook Verification and Ingestion
Given a webhook request from GitHub Actions, GitLab CI, CircleCI, or Jenkins with a valid provider-specific signature and shared secret When the request is received by EchoLens Then the signature is validated per provider spec and a 200 OK is returned within 500 ms and the event is enqueued for processing Given a webhook request with an invalid or missing signature When the request is received by EchoLens Then the request is rejected with 401 Unauthorized, an error code of "invalid_signature" is returned, and no event is persisted Given a valid webhook request with a provider timestamp within ±5 minutes clock skew When processed Then the request is accepted; if the timestamp is older than 10 minutes it is rejected with 408 Request Timeout and not persisted Given provider outage or endpoint latency spikes When webhooks are received Then the ingestion endpoint sustains at least 200 requests per second with p95 latency under 800 ms without data loss
Payload Normalization and Required Fields
Given a provider-specific deploy payload (any supported provider) When EchoLens processes the event Then a normalized record is stored with fields: provider, service, repo, environment, version, commit_sha, artifact, region, start_ts, end_ts, idempotency_key Given a normalized record When validation runs Then start_ts and end_ts are stored in ISO 8601 UTC with millisecond precision, commit_sha is 7–40 hex chars when present, and unknown optional fields are set to null with reason code Given an event missing all of {version, commit_sha} or missing start_ts When validation runs Then the event is rejected with 422 Unprocessable Entity and a machine-readable error payload; nothing is persisted Given partial provider payloads that lack region or artifact When normalized Then region and artifact are stored as null without causing validation failure
Idempotency and Duplicate Suppression
Given two or more deliveries of the same deploy event with the same idempotency_key within a 14-day window When processed Then exactly one normalized record exists and subsequent deliveries return 200 OK with an "idempotent_replay" indicator Given a delivery without an explicit idempotency_key When processed Then a deterministic key is generated from provider, external_run_id, commit_sha (if present), and environment, and duplicate suppression works identically Given up to 10 duplicate deliveries arriving concurrently When processed under load Then no duplicate records are created (exactly-once effect) and p95 processing latency remains under 1 second
Retry, Backoff, and Dead-Letter Handling
Given a transient processing failure (e.g., database unavailable) When an event fails during processing Then the system retries up to 5 times with exponential backoff (1s, 2s, 4s, 8s, 16s) capped at 60s, with total retry time not exceeding 10 minutes Given an event that still fails after max retries When processing concludes Then the event is moved to a dead-letter queue with failure metadata (error codes, last exception, attempts, first_seen_ts) Given an operator with admin privileges When invoking the DLQ replay endpoint for selected events Then events are re-queued preserving original idempotency_key and are processed without creating duplicates Given network partitions or message reordering When retries occur Then the system maintains at-least-once delivery semantics and achieves exactly-once storage via idempotency
REST Backfill of Historical Deploys
Given a user provides provider credentials and selects a date range up to 90 days When a backfill job is started Then EchoLens fetches historical deploys, respects provider API rate limits, and displays progress (queued, in_progress, completed) in the UI Given previously ingested deploys exist for the selected range When backfill runs Then duplicates are skipped via idempotency and only new records are persisted Given normal provider API performance When backfill runs Then sustained throughput is at least 500 deploy events per minute and the job can be paused and resumed without data loss Given API errors or timeouts from the provider When backfill runs Then errors are retried per the retry policy; persistent failures are surfaced in the UI with counts and downloadable error details
Setup UI: Configuration, Verification, and Secret Rotation
Given an Org Admin opens Integrations > CI/CD Webhooks When adding a provider configuration Then the UI displays a per-environment webhook URL and generates a secret; the user can copy both with one click Given the user clicks "Verify" in the setup UI When a signed test event is sent and received Then the UI shows Verified within 5 seconds upon success or actionable error details upon failure; verification status is persisted Given secret rotation is initiated When a new secret is generated Then both old and new secrets are accepted for 30 minutes, after which the old secret is revoked; all changes are audit-logged with user, timestamp, and diff Given a user without admin privileges When attempting to create, edit, or rotate a CI/CD integration Then the action is denied with a 403 and no changes are made
Environment Mapping and Timeline Correlation
Given provider environment labels or branches are mapped in the UI to EchoLens environments (e.g., Production, Staging) When a deploy event is ingested Then the event is resolved to an EchoLens environment; unmapped labels are routed to "Unmapped" and flagged for review Given stored deploy events with start_ts and end_ts When queried for a time window Then the timeline API returns all events that intersect the window ordered by start_ts with 1-second resolution and filterable by service, repo, and region Given a theme spike occurs in EchoLens When root-cause hinting requests correlated deploys Then the service returns up to the 3 most recent deploys per affected service within the spike window (±24h) along with links to commit/run and environment context
Feature Flag Toggle Tracking
"As a PM, I want flag toggle histories captured with audiences and rollout details so that I can identify if a spike is driven by a flag change."
Description

Capture feature flag change events from providers (LaunchDarkly, Optimizely, Split) and custom systems. Ingest toggles with flag key, variation, target rules, rollout percentage, environment, service/app context, and actor. Maintain an immutable change history and map flags to product areas and themes. Align timestamps to feedback events and compute active audience size where available. Expose a simple SDK/webhook for homegrown flags. This enables attribution of spikes to progressive rollouts and targeted cohorts, speeding root-cause identification.

Acceptance Criteria
Webhook Ingestion from LaunchDarkly/Optimizely/Split
Given a valid webhook payload from a supported provider including flagKey, variation, targetRules, rolloutPercent, environment, service, actorId, occurredAt, and providerEventId When the payload is POSTed to /integrations/feature-flags/webhook with valid signature/auth Then the event is accepted with HTTP 202 and p95 response time <= 300ms And the payload is validated against the provider-specific schema; invalid requests return HTTP 400 with field-level error codes And a normalized internal event is persisted with all fields and provider metadata within 2s of receipt And idempotency ensures duplicate providerEventId within 24h results in a single stored event and HTTP 202 And an append-only record with immutable eventId is created
Timestamp Normalization and Feedback Alignment
Given a feature flag change event with occurredAt in any timezone When the event is processed Then occurredAt is stored as an ISO-8601 UTC timestamp with millisecond precision and receivedAt is recorded separately And for any feedback record queried at timestamp T, the system returns the most recent flag state effective at or before T in the same environment and service And p95 latency to resolve flag state for 1,000 feedback records in batch is <= 200ms per record
Active Audience Size Computation
Given an environment with userBaseSize = 100,000 and a flag event with rolloutPercent = 10 When the event is processed Then activeAudience.size is computed as 10,000 and stored with source = "computed" and confidence = "estimated" And if provider supplies evaluationCount, activeAudience.size equals evaluationCount with source = "provider" and confidence = "exact" And if userBaseSize is unknown, activeAudience.status = "unknown" and no size value is stored And the computation completes within 1s
Custom Flags SDK/Webhook
Given a POST to /v1/flags/events with x-api-key auth and a body conforming to schema v1 (eventId, flagKey, variation, targetRules, rolloutPercent, environment, service, actorId, occurredAt) When the request is valid Then the API returns HTTP 202 and enqueues the event; duplicates by eventId within 7 days are deduplicated And invalid schema returns HTTP 400 with machine-readable error codes; missing/invalid auth returns 401/403; rate limits return 429 with Retry-After And the endpoint supports at-least-once delivery with idempotency guarantees and exposes retry guidance via response headers
Mapping Flags to Product Areas and Themes
Given mapping rules are configured (explicit mapping table and key-prefix rules) When a flag event is ingested Then the event is linked to exactly one ProductArea and zero or more Themes at ingest time And unmapped flags are assigned to "Unmapped" and an admin notification is sent within 5 minutes And updates to mapping rules take effect for new events within 1 minute and a backfill job can be run to apply changes to historical events with progress reporting
Immutable Change History and Auditability
Given a stored flag change event When an API client attempts to update or delete the event via public or internal endpoints Then the operation is rejected with HTTP 405/409 and no data is modified And the event store enforces append-only constraints and retains events for >= 365 days And each event includes audit fields (actorId, provider, signature/verification status, receivedAt, processedAt) And an export endpoint returns a chronological, tamper-evident sequence ordered by occurredAt then eventId
Environment and Service Context Isolation
Given events from multiple environments (prod, staging) and services/apps When querying correlations or resolving flag state for feedback in prod Then only prod events are used by default; non-prod can be included via explicit filter And cross-service queries return only events matching the requested service/app unless "all services" is specified And environment and service filters are required in public APIs and validated; invalid values return HTTP 400
App Version & Platform Metadata Ingestion
"As a CX lead, I want version and platform metadata linked to feedback so that I can isolate spikes to affected versions and devices."
Description

Ingest and normalize app/service version and platform metadata from client and server events. Support SDK/headers and sources like Segment/RudderStack to capture semantic version, build number, OS version, device model, app store channel, and backend service versions. Maintain adoption curves per version and map incoming feedback to versions and platforms. Provide validation of semver formats and aliasing for hotfix builds. This enables isolating spikes to specific versions/devices to narrow investigation scope.

Acceptance Criteria
Normalize and Validate App Version Semver
- Given an incoming event contains an app version string, When the string conforms to SemVer 2.0.0 (e.g., 1.2.3, 1.2.3-alpha.1+45), Then it is accepted and stored in normalized fields (major, minor, patch, preRelease, build). - Given an incoming event contains an app version string, When the string does not conform to SemVer 2.0.0, Then the event is ingested with version set to null and a validation error is logged with code VERSION_INVALID. - Given an accepted version, When persisted, Then a canonical normalizedVersion is stored exactly in format MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD] and is case-insensitive for identifiers. - Given a batch of 1,000 mixed valid/invalid versions, When processed, Then 100% of valid versions are accepted and 100% of invalid versions are flagged with VERSION_INVALID.
Capture Platform and Device Metadata from SDK Headers
- Given client SDK events include headers/fields for osVersion, deviceModel, platform (iOS|Android|Web|Desktop), buildNumber, and appStoreChannel, When ingested, Then each field is stored in normalized columns with non-empty strings trimmed, and platform enumerated. - Given any of the above fields are missing or empty, When ingested, Then the field is stored as null and a metadataMissing tag includes the missing field names. - Given osVersion values, When they are version-like (e.g., 17.5.1, 14.4), Then they are parsed and stored as strings without mutation; non-parsable values are stored as raw and flagged OS_VERSION_INVALID. - Given deviceModel strings exceeding 64 chars, When ingested, Then they are truncated to 64 chars with safe UTF-8 and TRUNCATED flag set.
Ingest Version Metadata via Segment/RudderStack
- Given events arrive via Segment or RudderStack, When the payload includes context.app.version, context.app.build, context.os.name, context.os.version, context.device.model, context.device.type, and context.library.name, Then the pipeline maps them to appVersion, buildNumber, osName, osVersion, deviceModel, platform, and source with lossless fidelity. - Given context.app.version is absent but properties.appVersion exists, When ingested, Then properties.appVersion is used as appVersion with source set to "fallback". - Given payloads with unexpected keys, When ingested, Then ingestion succeeds and unknown keys are ignored without dropping the event. - Given a batch of 10,000 events from Segment/RudderStack, When processed, Then throughput is at least 2,000 events/second with <1% mapping errors.
Alias Hotfix Builds to Base Version
- Given a configured alias rule (e.g., 1.2.3+* -> 1.2.3; 1.2.3-hotfix.* -> 1.2.3), When an event with appVersion 1.2.3+45 or 1.2.3-hotfix.1 is ingested, Then baseVersion is stored as 1.2.3 and aliasApplied=true. - Given no matching alias rule exists, When an event is ingested, Then baseVersion equals normalizedVersion and aliasApplied=false. - Given analytics queries group by baseVersion, When executed, Then all aliased variants roll up under the base version with counts equal to the sum of variants.
Generate Adoption Curves per App Version
- Given appVersion events per platform, When the daily job runs at 00:15 UTC, Then adoptionCurves are computed for the last 90 days per appVersion and platform as percentage of active devices that day. - Given new events arrive after the daily job, When streaming updates occur, Then adoptionCurves incrementally update within 5 minutes (p95) of event arrival. - Given an API request GET /adoption?appId=...&platform=iOS&days=30, When executed, Then it returns a time series with date, appVersion, percentAdoption where sums per day <= 100% and response time <= 500 ms for 30 days.
Map Feedback Items to Version and Platform
- Given a feedback item (review, ticket, chat, email) has explicit version/platform metadata, When ingested, Then the item is linked to that appVersion, baseVersion, platform, osVersion, and deviceModel. - Given a feedback item lacks explicit metadata, When the user/session has a lastSeen event within 24 hours before feedback timestamp, Then the item is linked to that lastSeen appVersion and platform; otherwise it is marked UNRESOLVED_METADATA. - Given a Root-Cause Hints query for top versions, When executed over a sample with 1,000 feedback items, Then at least 95% of items with eligible telemetry are correctly mapped to a version (verified by fixture ground truth).
Ingest Backend Service Versions from Server Events
- Given server-side events/logs with headers X-Service-Name and X-Service-Version, When ingested, Then serviceName and serviceVersion (SemVer-validated) are stored and normalized with the same rules as appVersion. - Given requests include requestId/sessionId present in both client and server events, When ingested, Then feedback-to-serviceVersion linkage is established via join on requestId/sessionId within a 5-minute window. - Given invalid service version strings, When encountered, Then serviceVersion is set to null and SERVICE_VERSION_INVALID is logged without dropping the event.
Incident Log Connector
"As an on-call lead, I want incidents pulled into EchoLens with severity and service context so that spikes attributable to outages are flagged automatically."
Description

Integrate with incident management tools (PagerDuty, Opsgenie) and Statuspage to pull incident events and updates via webhooks/polling. Normalize to include status, severity, impacted services, start/end times, responder notes, and postmortem links. Detect overlapping and cascading incidents and align them on the EchoLens timeline. Provide filtering by environment and service. This context allows Root-Cause Hints to attribute spikes to outages and reliability issues quickly.

Acceptance Criteria
PagerDuty and Opsgenie Webhook Ingestion
Given valid webhook secrets are configured for PagerDuty and Opsgenie When the provider sends incident events (triggered, acknowledged, resolved) for the connected account Then EchoLens validates the signature, ingests the payload, and upserts the incident within 10 seconds of receipt And provider-specific fields are mapped to the normalized schema (status, severity, impacted_services, start_time, end_time, responder_notes, postmortem_link) And duplicate deliveries are deduplicated by provider incident id and last_update timestamp And an audit record with source=webhook and provider name is stored
Statuspage Polling and Update Handling
Given a connected Statuspage page_id and API token When polling runs at the default 60s interval Then new and updated incidents and component impacts are fetched and upserted without duplication And severity and impacted_services are derived from Statuspage impact and affected_components mappings And polling handles 429/5xx with exponential backoff up to 5 minutes and resumes automatically And a checkpoint cursor/time is persisted to support delta fetching across restarts
Normalized Incident Schema Output
Given incidents ingested from any supported source When clients request incidents via the internal Incident API Then each incident includes required fields: id, source, status in {triggered, acknowledged, resolved}, severity in {sev1, sev2, sev3, sev4}, impacted_services[], environment in {prod, staging, dev, unknown}, start_time (ISO8601), end_time (nullable ISO8601), responder_notes (string[]), postmortem_link (URL|null) And incidents failing schema validation are quarantined (not published) and an error log is emitted with reason And normalization latency per incident does not exceed 2 seconds at p95 under 100 events/min
Overlap/Cascade Detection and Timeline Alignment
Given two or more incidents have overlapping time ranges and at least one common impacted service When a timeline is requested for a window that intersects these incidents Then the incidents appear as parallel aligned bands spanning their start_time to end_time on the EchoLens timeline And cascades are labeled when an incident on the same service starts within 30 minutes after a prior incident ended And the Root-Cause Hints API returns overlapping incidents for any given spike window sorted by (temporal proximity asc, severity desc)
Environment and Service Filtering
Given incidents are tagged with environment and impacted_services When a query specifies environment filters and a list of services Then only incidents matching all filters are returned by the Incident API and displayed on the timeline And responses for filtered queries of up to 10,000 incidents complete within 500 ms at p95 And omitting filters returns incidents for all environments and services within the requested time range
Backfill on Enable/Reconnect and Freshness Health
Given the connector is newly enabled or has been offline for any duration When it starts or reconnects Then it backfills incidents from all sources for the past 30 days without creating duplicates And data freshness per source (time since last successful fetch) is exposed via a health endpoint and is <= 5 minutes during steady state And exceeding 5 minutes freshness marks the source status as WARN and emits an alert event
Correlation & Confidence Scoring Engine
"As a PM, I want statistically sound root-cause hints with confidence scores and rationale so that I can prioritize investigations quickly."
Description

Build a streaming analysis service that detects feedback spikes (e.g., change-point detection) and correlates them with deploys, flag changes, version rollouts, and incidents within configurable windows. Compute a ranked list of likely drivers using uplift against baselines, cohort alignment, temporal proximity, and volume weighting, producing a confidence score and concise rationale. Include guardrails to reduce false positives (e.g., control themes, seasonality, cooldowns) and tunable thresholds. Target end-to-hint latency under 2 minutes with horizontal scalability and audit logs of decisions.

Acceptance Criteria
Real-Time Spike Detection (<= 2-Minute Latency)
Given streaming feedback events tagged with theme_id and timestamps and default detector settings (window=10m, min_volume=30, uplift>=40%, p_value<=0.01) When the observed count for a theme crosses the detection threshold within the analysis window Then the system emits a spike_detected event and produces a root-cause hint within 120 seconds of the threshold crossing And the emitted record contains theme_id, baseline_rate, observed_rate, uplift_percent, p_value, detection_method, window_start, window_end, and detector_version
Driver Correlation and Ranking
Given an active spike and a catalog of deploys, feature flag changes, app version rollouts, and incident logs with timestamps and targeting metadata When correlation runs with window_config (pre=60m, post=120m) Then the system computes, for each candidate driver, temporal overlap, cohort alignment score, uplift vs baseline, and volume weighting, and assigns a confidence score in [0.0,1.0] And returns a ranked list (max 5) sorted by confidence with ties broken by recency And includes for each driver: driver_id, driver_type, window_used, evidence_metrics, confidence, and a one-sentence rationale referencing the strongest evidence component And any driver with confidence < 0.2 is excluded from the list
False-Positive Guardrails
Given seasonality models by hour-of-day and day-of-week and a configured set of control themes When an apparent spike is explainable by seasonality (adjusted z-score<=1) or control themes move in parallel (Pearson r>=0.7 within the window) Then no hint is emitted for that theme And a per-theme cooldown (default 60m) suppresses repeat spikes unless observed rate increases by an additional 25% over the last fired level And each suppression is recorded with reason=seasonality|control_theme|cooldown in the audit log
Tunable Thresholds and Windows
Given an admin updates detector thresholds (min_volume, uplift_threshold, p_value) and correlation windows via API or UI When the change is saved Then the new settings are validated (min_volume 1–1000, uplift_threshold 5–500%, p_value in (0,0.5], windows 1–720m) and persisted with version and actor And the running service applies the new version within 5 minutes without downtime And the audit log records previous and new values, actor_id, timestamp, and optional change_reason
Auditability of Decisions
Given any spike detection, correlation, suppression, or configuration change occurs When a user requests the audit trail for a theme or driver within a date range Then the system returns records containing inputs (counts, baselines, metadata), algorithms and versions, parameters used, outputs (scores, rationale), and timing metrics And records are immutable, queryable by id, theme_id, driver_id, and time, and retained for at least 90 days And no user PII is stored in audit logs
Cohort Alignment Correctness
Given a test dataset where app version v3.2.1 rollout to 30% of users introduces a known issue affecting theme T When a spike occurs on T during the rollout window Then the top-ranked driver is the v3.2.1 rollout with confidence >= 0.6 and a rationale referencing affected cohort share and temporal alignment And users not on v3.2.1 contribute <= 20% of the spike volume after cohort filtering And presence of matching feature-flag exposures increases the confidence compared to a run without exposure data
Horizontal Scalability and Backpressure
Given sustained ingest of 2,000 feedback events per minute with 10 concurrent active themes and 50 candidate drivers When load doubles to 4,000 events per minute for 15 minutes Then p95 end-to-hint latency remains <= 120s and p99 <= 180s with zero data loss and at-least-once processing semantics And autoscaling increases consumer/concurrency to maintain SLA and scales back down after load returns to baseline And when downstream sinks are slow, backpressure prevents queue overflow without dropping detection or correlation tasks
Hints Timeline Overlay UI
"As a PM, I want a timeline overlay that shows feedback spikes alongside deploys, flags, versions, and incidents so that I can visually validate the likely driver."
Description

Add a dedicated panel within the Theme Spike view that overlays feedback volume with deploys, flag toggles, version adoption, and incidents. Display ranked hint chips with confidence and a "why" tooltip summarizing evidence (e.g., cohort overlap, uplift). Provide filters by service, environment, version, and timeframe, with deep links to the source event (CI/CD run, flag dashboard, incident). Ensure accessibility, responsive design, and export/share options. This makes hints explorable and verifiable at a glance, accelerating triage.

Acceptance Criteria
Toggleable Timeline Overlay with Event Layers
Given the user is in the Theme Spike view with Root-Cause Hints enabled When the user opens the Hints Timeline panel Then a timeline chart renders showing feedback volume over the selected timeframe And overlays are visible for deploys, feature flag toggles, app version adoption milestones, and incidents, each with distinct color/shape and a legend And initial render completes within 2 seconds for up to 30 days of data and 5,000 overlay events And panning/zooming or timeframe changes update the chart within 300 ms And new source events ingested appear on the chart within 60 seconds without page reload
Ranked Hint Chips with Confidence and Why Tooltip
Given timeline data and correlated events exist When the panel loads Then up to 6 hint chips display, sorted by confidence descending And each chip shows driver type (deploy/flag/version/incident), label, and confidence as a whole-number percent (0–100%) And hovering or focusing a chip shows a "why" tooltip containing at least uplift percentage vs baseline, cohort overlap percentage, and count of linked feedback items with representative timestamps And clicking a chip highlights matching overlays on the chart and applies a temporary filter badge And chip selection is toggleable and supports multi-select
Multi-Facet Filters with Live Updates
Given filters for service, environment, version, and timeframe are visible When the user selects or deselects filter values (multi-select supported, default All) Then the chart, hint chips, and counts update within 500 ms And the applied filters persist in the URL as query parameters and in session storage And clearing filters resets to All and removes related query parameters And timeframe supports presets (24h, 7d, 14d, 30d) and a custom range via calendar picker
Deep Links to Source Systems from Events
Given overlay events are displayed When the user clicks an event or its deep-link control Then a new tab opens to the configured source (CI/CD run page, feature flag dashboard, incident record, or version release page) and loads with HTTP 200 And the link includes identifying parameters (service, environment, commit SHA or flag key, incident ID, version) and UTM source=EchoLens&feature=hints_timeline And if the integration is not configured, the deep-link control is disabled and a tooltip explains configuration steps And if the target returns a non-200 response, a non-blocking error toast appears with a retry option
Accessibility and Keyboard Navigation
Given a user navigates via keyboard and assistive technology When focusing elements within the Hints Timeline panel Then all interactive controls are reachable via Tab/Shift+Tab with visible focus indicators and ARIA labels And tooltips for hint chips and overlay events are accessible via keyboard (Enter/Space) and dismissible via Esc And screen readers announce chip label, driver type, confidence, and a concise why summary And text and key chart elements meet WCAG 2.2 AA contrast (>= 4.5:1) And color is not the sole means of encoding overlays (distinct shapes/patterns are used)
Responsive Layout Across Devices
Given the panel is viewed on different devices When rendered at 320x568, 768x1024, and 1440x900 viewports Then content adapts without horizontal scrolling, with readable charts and legends And at <=768px the legend collapses into a dropdown and the timeline uses a simplified sparkline with tap targets >= 44x44 px And touch interactions support pinch-zoom and pan without interfering with page scroll And orientation changes reflow content within 300 ms
Export and Share of Timeline and Hints
Given the Hints Timeline panel is populated When the user clicks Export Then a PNG of the current timeline view (including overlays and active filters) is generated within 5 seconds and downloads with a filename that includes the date range And a CSV of hint chips (label, driver type, confidence, uplift, overlap, linked feedback count) generates within 3 seconds And clicking Share copies a URL with all active filters, selected chips, and timeframe to the clipboard, confirmed by a toast And exports include a footer timestamp (UTC) and organization name And each exported file (PNG, CSV) is <= 5 MB
Auto-Sync Hints to Jira/Linear
"As a PM, I want root-cause hints to auto-populate in Jira/Linear tickets so that engineering has immediate context without manual copy-paste."
Description

Extend existing one-click sync to include root-cause hint details on created/updated Jira/Linear issues: top driver, confidence, evidence summary, and source links. Support field mapping to custom fields, idempotent updates on hint revisions, and permission-aware posting. Optionally add comments when confidence changes materially. This ensures engineers receive actionable context without manual copy/paste, tightening the feedback-to-fix loop.

Acceptance Criteria
Create New Issue with Root-Cause Hint Details
Given a theme has a root-cause hint and no linked external issue When the user clicks one-click Sync to Jira or Linear Then create a new issue in the selected project/team and populate: Top Driver, Confidence, Evidence Summary, and Source Links And include an EchoLens external-id/marker on the issue for future idempotent updates And return the created issue key/id and URL to EchoLens
Update Existing Issue Idempotently on Hint Revision
Given a theme is linked to an existing Jira/Linear issue and the hint content has changed When Sync is invoked (manual or scheduled) Then update only the mapped hint fields that have changed and leave unrelated fields untouched And do not create a duplicate issue or duplicate comments And if nothing changed, perform no external updates and record a no-op result
Apply Custom Field Mapping for Hint Fields
Given workspace-level field mapping is configured for Top Driver, Confidence, Evidence Summary, and Source Links When syncing to Jira/Linear Then write each value to the specified custom/system fields per mapping And if a mapped field is missing or read-only, append the value under a "Root-Cause Hint" section in the description and surface a mapping warning And prevent sync with a clear error if the mapping is invalid (e.g., wrong field type)
Permission-Aware Sync and Fallbacks
Given the integration token has limited permissions on the target issue When syncing Then perform only allowed operations (create/update/comment) and skip forbidden ones And if field updates are forbidden but comments are allowed, post the hint details as a comment instead And if neither updates nor comments are permitted, fail gracefully with a surfaced error and make no partial writes
Material Confidence Change Adds Comment
Given "Comment on material confidence change" is enabled with a threshold (default 10 percentage points) When the hint confidence changes by ≥ threshold or crosses a confidence band (e.g., Low/Med/High) Then add a single comment noting previous vs new confidence, brief reason/evidence summary, and timestamp, with a link back to the theme in EchoLens And if the change is below threshold, do not post a comment And repeated syncs for the same change do not duplicate the comment
Include Evidence Summary and Source Links
Given a theme has multiple evidence items (tickets, chats, reviews, emails) When syncing Then include a concise Evidence Summary (max 500 chars) and up to 3 representative Source Links that are clickable in the destination And only include links that are accessible to the destination users; replace inaccessible links with redacted labels And ensure formatting renders correctly in Jira and Linear markdown
Jira and Linear Support Parity
Given the destination is Jira or Linear When syncing new issues and updates with mapped fields Then the behavior, field mapping, idempotency, comments, and permissions handling work consistently on both platforms And for Jira, support both company-managed and team-managed projects; for Linear, support workspace custom fields and labels And automated tests cover both platforms for create, update, no-op, permission-denied, and mapping-error paths

Blast Radius

Estimates affected users, ARR at risk, and impacted segments in real time, with velocity and confidence. Helps teams prioritize and justify action to stakeholders when minutes matter.

Requirements

Real-time Impact Estimation Engine
"As a product manager, I want real-time counts of unique affected users and accounts per theme so that I can size impact quickly and triage with confidence."
Description

Compute live estimates of affected unique users and accounts for each emerging theme by processing EchoLens’ ingested reviews, emails, tickets, and chats in a streaming pipeline. Maintain an identity graph to deduplicate users across channels, apply sampling correction, and classify impact at theme, product, and service levels. Provide sub-minute latency, resilience to bursts, and backfill for late-arriving events. Expose an internal API and cache for UI and alert consumers, with safeguards for PII and rate limits.

Acceptance Criteria
Sub-minute Theme Impact Updates
Given streaming ingestion of reviews, emails, tickets, and chats mapped to Theme A When 100 new events are ingested within 1 minute Then API GET /impact/themes/{id} returns fields unique_users, unique_accounts, velocity_1m, velocity_5m, velocity_15m, confidence And p95 end-to-end latency from first event ingestion to availability <= 60s (p99 <= 120s) And unique user and account counts are deduplicated across channels via the identity graph
Cross-Channel Identity Deduplication Quality
Given a labeled validation set of >=10,000 user identities spanning email, chat, ticket, and review channels When the identity graph performs deduplication on the stream Then precision >= 98% and recall >= 95% on the validation set And merges/splits trigger recomputation of impacted counts for affected themes within 60 seconds p95 And dedup decisions are idempotent and auditable via recorded match rationale and timestamps
Sampling Correction Accuracy for Partial Ingestion
Given a channel with a known 50% sampling rate and a synthetic dataset with ground-truth unique users for Theme B When 2,000 sampled events are processed in the stream Then the corrected unique_users estimate is within ±10% of ground truth at 95% confidence And estimates are labeled corrected=true with an exposed correction_factor and confidence_interval And correction does not overcount users already observed via unsampled channels
Hierarchical Impact Classification (Theme/Product/Service)
Given events mapped to Theme C with associated Product P and Service S When the stream processes these events Then API returns impact at theme, product, and service levels with fields unique_users and unique_accounts per level And rollups are consistent: sum of child themes equals parent theme for the same dedup scope, and products roll up to their services without double-counting And if an event matches multiple themes, the highest-confidence theme is selected and the decision rationale is recorded
Late-Arriving Event Backfill and Correction
Given events for Theme D may arrive out-of-order up to 24 hours late When a late event is ingested after the watermark for its window Then the system updates counts and velocities within 120 seconds p95 of arrival And corrections are exposed via API with a version increment and corrected_at timestamp without double-counting And historical cache entries affected by the correction are invalidated within 5 seconds
Burst Resilience and Throughput SLA
Given a sudden 10x spike in incoming events sustained for 5 minutes When the system processes the spike Then there is no data loss (at-least-once delivery with idempotent dedup verified by contiguous source offsets) And processing latency remains <= 120 seconds p99 during the spike and returns to <= 60 seconds p95 within 10 minutes after And autoscaling triggers within 2 minutes of spike onset and queue depth remains below the defined backpressure threshold And an alert is emitted if latency SLOs are breached
Internal Impact API, Caching, and Rate Limiting with PII Safeguards
Given an authenticated service client with Consumer role When it requests GET /impact/themes/{id} and related endpoints repeatedly Then p95 latency <= 200 ms on cache hits and <= 800 ms on cache misses with cache TTL=5 seconds and stale-while-revalidate up to 5 seconds And per-client rate limit is 1,000 requests/minute; exceeding returns HTTP 429 with Retry-After header And API payloads, caches, and logs contain no PII (names, emails, phone, free text); identifiers are hashed or redacted; transport uses TLS 1.2+ and data at rest is encrypted And 30-day rolling availability SLO is >= 99.9%
Velocity and Confidence Scoring
"As a CX lead, I want velocity and confidence measures on emerging issues so that I can distinguish true spikes from noise and act appropriately."
Description

Calculate velocity (rate-of-change of incidents over time) and a normalized 0–100 confidence score per theme using time-windowed counts, Bayesian smoothing, and anomaly detection. Incorporate signal quality (source mix, sample size, recency) and model uncertainty to prevent overreaction to noise. Surface trend directionality, half-life decay, and error bounds to the UI and exports, with auto-adjusted windows optimized for teams that ship weekly.

Acceptance Criteria
Weekly Velocity Computation and Directionality
Given a theme with time-stamped incident counts and default cadence set to weekly (7-day windows) When the system computes velocity Then it must: - Use Bayesian-smoothed counts over the current 7-day window and the immediately preceding 7-day window - Calculate velocity as (current_smoothed - previous_smoothed) / max(previous_smoothed, 1) expressed as a percentage with one decimal - Classify trend_direction as Up if velocity >= +5%, Down if <= -5%, else Flat - Update velocity and trend_direction within 60 seconds of ingesting new data - Persist window_start, window_end, and counts_used metadata with the result
Confidence Score Normalization and Components
Given a theme with incidents across sources and timestamps When the system computes confidence Then it must: - Output a normalized integer in [0,100] - Increase monotonically (non-decreasing) with additional corroborating incidents holding other factors constant - Include components: sample_size, source_mix_diversity, recency_weight, model_uncertainty_dampening in the breakdown payload - Set confidence >= 80 when sample_size >= 50, sources >= 3, median_event_age <= 3 days, and model_uncertainty <= 0.1 - Set confidence <= 40 when sample_size < 5 or sources = 1 or median_event_age > 14 days - Recompute and publish within 60 seconds of new data
Anomaly Suppression and Noise Resistance for Sparse Themes
Given a theme averaging < 1 incident/day over the last 28 days When a single-day spike occurs that is not sustained into the following day Then the system must: - Flag the spike as transient_anomaly = true - Apply Bayesian smoothing such that velocity does not exceed +100% unless the spike persists for >= 2 consecutive days - Reduce confidence by at least 20 points for the affected window compared to baseline - Mark trend_direction as Flat or Up with a low_confidence tag; never Down due to a single positive spike - Clear transient_anomaly automatically if no follow-on spike occurs within 48 hours
Auto-Adjusted Windows and Half-Life Decay
Given a theme with recent activity When selecting analysis windows Then the system must: - Use 7-day windows by default; if current 7-day smoothed_count < 10, expand to 14 days; if current 14-day smoothed_count < 10, expand to 28 days; otherwise use the smallest window meeting the threshold - Apply exponential decay to all counts with a half_life of 14 days - Include window_length_days and half_life_days in the payload and UI tooltip - Re-evaluate window selection whenever new data arrives and avoid flipping window length more than once per hour - Backfill velocity and confidence historically when window length changes to keep week-over-week comparability
UI and Export Surfaces for Velocity, Confidence, and Error Bounds
Given the theme details panel and export endpoints (CSV and API) When a theme is viewed or exported Then it must display/contain: - velocity_percent (one decimal), trend_direction (Up|Down|Flat), confidence_score (0–100), lower_ci and upper_ci for velocity at the 90% level, half_life_days, window_length_days, and last_updated_at (ISO-8601) - A tooltip or details section listing the components contributing to confidence and their weights - Consistent values between UI and exports (absolute difference <= 0.1 percentage points for velocity; exact match for integers) - Error bounds present for all themes; if not computable, fields set to null and a not_computable_reason provided - Export generation completes within 10 seconds for up to 10,000 themes
Recalculation, Late Data Handling, and Auditability
Given streaming ingestion and late-arriving events up to T+24h When new or late data is ingested for a theme Then the system must: - Recompute velocity, confidence, and error bounds within 2 minutes - Version each computed record with a monotonically increasing calc_version and keep the last 5 versions - Ensure idempotent recomputation so repeated payloads produce the same outputs - Emit an audit_log entry capturing inputs (window, counts, anomalies) and outputs (velocity, CI, confidence) for traceability - Never decrease confidence solely due to older events being reprocessed unless component weights or data actually change
ARR at Risk Modeling
"As a finance-aware PM, I want an accurate ARR at risk estimate by theme so that I can prioritize fixes that protect revenue."
Description

Join impacted accounts to ARR using CRM and billing connectors (e.g., Stripe, Salesforce, HubSpot) and account hierarchies to estimate gross and net ARR at risk in real time. Weight by license utilization and seat coverage, support multiple currencies with daily FX normalization, and attribute ARR to themes. Provide scenario toggles (severity × coverage) and a permissions-aware breakdown suitable for finance and executive audiences.

Acceptance Criteria
CRM/Billing Connector Join Accuracy
Given active connectors to Stripe, Salesforce, and HubSpot with ARR fields mapped and last successful sync ≤ 15 minutes ago When impacted accounts are identified from themes and lookups run Then the system matches ≥ 98% of impacted accounts to ARR records via deterministic precedence (account_id > domain > CRM external_id) And unmapped accounts are surfaced with reason codes (missing id, conflicting records, currency missing) And the sum of matched ARR reconciles to source totals within ±0.5% for the same cohort and timestamp
Account Hierarchies and Rollups
Given parent-child account hierarchies exist in the CRM with up to 3 levels When multiple child accounts under the same parent are impacted Then gross ARR at risk is rolled up to the parent without double-counting And a user toggle switches between Parent Rollup and Child Detail views And changes to hierarchy or impact flags propagate to rollups within 5 seconds
Gross vs Net ARR at Risk Computation
Given impacted accounts with ARR, discount rate, contract status, and renewal dates When ARR at risk is calculated Then Gross ARR at risk = sum(ARR of impacted active accounts) And Net ARR at risk = Gross ARR at risk minus excluded churned/expired contracts and discount-adjusted amounts And the output includes timestamp, calculation version, and both figures side-by-side And confidence score (0–1) is displayed for each calculation window
Severity × Coverage Toggles and Recalculation
Given UI toggles for Severity (Low=0.25, Medium=0.5, High=1.0) and Coverage (Observed seat coverage %, Inferred segment coverage %) When a user adjusts any toggle or selects a preset scenario Then modeled ARR at risk applies multiplier = Severity × Coverage × License utilization factor (active seats/licensed seats) And recalculation completes and updates charts and totals within 2 seconds And the delta from previous scenario is displayed And the selected scenario is preserved in sharable links and exports
Multi-Currency with Daily FX Normalization
Given ARR values in USD, EUR, GBP, and JPY and a base currency configured When totals are computed Then conversions use the latest daily FX rates refreshed at 00:00 UTC and record the rate date per line item And if a rate is unavailable, the last business day’s rate is used and flagged And totals match manual verification within ±0.5% with 2-decimal rounding And UI and exports display both native and base currency where applicable
Theme-Level ARR Attribution
Given accounts produce messages assigned to multiple themes with per-message confidence When attributing ARR to themes Then an account’s ARR at risk is apportioned across themes proportional to message-weighted confidence And per-account per-theme allocations sum to the account-level modeled ARR at risk within ±0.01 of base currency And each theme displays Gross and Modeled ARR at risk, 7-day velocity, and attribution confidence
Permissions-Aware Finance/Executive Breakdown
Given role-based access (Finance, Executive, PM, CX) and data access policies When users view the ARR at risk breakdown Then users see only permitted accounts and segments, with PII masked for unauthorized roles And Finance/Executive views include segment breakdowns (plan, region, industry), gross vs net totals, currency, and FX date And all accesses are logged with user, timestamp, and filter state for audit
Segment Detection and Attribution
"As a product operations analyst, I want to see which customer segments are most affected so that I can target mitigation and communication."
Description

Identify and rank impacted segments (plan tier, region, platform, customer size, industry, lifecycle stage) by correlating incident metadata and account tags. Provide top-N segment surfacing, filterable drilldowns, and suppression of low-signal segments. Persist segment definitions, allow custom dimensions via API, and handle overlapping memberships with transparent attribution logic.

Acceptance Criteria
Top-N Impacted Segments Surfacing
Given an active incident with linked feedback and tagged accounts across supported dimensions When segment detection runs due to incident creation or update Then the UI displays the top N segments (default 5; configurable 1–20) ranked by ARR at risk (desc), showing for each: dimension, value(s), affected accounts, affected users, ARR at risk (USD), velocity (/hr), confidence (0–1), and evidence count And the list updates within 30 seconds of relevant data changes And the sum of affected users and ARR across surfaced segments does not exceed the incident totals (per attribution rules) And a "View all segments" control reveals the full list sorted by the same ranking
Filterable Drilldowns by Segment
Given a surfaced segment is selected When the user opens Drilldown, applies filters (date range, dimension filters, product area), and clicks Apply Then the item list and impacted account list reflect only records matching the incident, selected segment, and active filters And the first page (up to 50 rows) renders within 2 seconds at p95 And total counts in headers equal the sum of listed items across pages (±1 due to rounding) And exporting the drilldown returns the same filtered set and totals
Low-Signal Segment Suppression
Given segment detection produces segments with varying signal strength When suppression rules are applied Then any segment is suppressed if ((impacted accounts < 5 OR evidence count < 3) AND confidence < 0.60) And suppressed segments are excluded from the top-N by default and accessible via "Show suppressed (k)" with a visible suppression reason per segment And workspace-level thresholds for impacted accounts, evidence count, and confidence are configurable and persisted
Persisted Segment Definitions and Versioning
Given an admin creates or edits a saved segment definition When Save is clicked Then the definition is persisted with id, name, owner, logic (dimension operators/values), and status=active, and becomes selectable in the segment picker within 10 seconds And editing creates a new version (v+1) without mutating historical incidents unless "Recompute history" is confirmed And deleting sets status=inactive, preventing future use while retaining historical references And all actions are recorded in the audit log with timestamp and actor
Custom Dimensions via API
Given a valid workspace API token When the client creates or updates a custom dimension via POST /v1/custom-dimensions and maps values to accounts via POST /v1/custom-dimensions/{id}/values Then the API validates schema (name, type: enum|string|number|boolean, allowed values for enum), enforces idempotency on upsert, and responds 201/200 with resource ids And successful writes are queryable in UI and detection within 5 minutes (p95) And invalid requests return 4xx with field-level error details; rate limits respond 429 with retry-after And custom dimensions appear in segment builders, drilldowns, and ranking within one detection cycle
Overlapping Membership Attribution Transparency
Given an account matches multiple segments for the same incident When metrics are computed for segments Then affected users and ARR are attributed fractionally across matched segments as 1/n per account (default), with total sums across segments equaling overall incident totals within ±0.1% And the UI displays an "Attribution: Fractional" label with tooltip explaining the formula and per-segment share And a per-account detail view shows how a selected account’s users and ARR were apportioned across its segments
Deterministic Ranking and Tie-breakers
Given two or more segments have identical primary metrics When ranking is computed Then segments are ordered by: ARR at risk desc, then affected users desc, then velocity desc, then confidence desc, then segment key asc And ranking is stable across repeated runs on unchanged data And the active sort order is indicated in the UI and preserved when filters or pagination are used
Live Theme Map Overlay
"As a product manager, I want blast radius metrics overlaid on the live theme map so that I can understand impact in context and drill into evidence."
Description

Overlay blast radius metrics (affected users, ARR at risk, velocity, confidence) on the existing EchoLens live theme map. Use color, size, and opacity encoding with badges, enable hover tooltips and click-through to evidence, and update in real time without page reloads. Respect map clustering and layout constraints, with performance budgets for large datasets and accessible contrast.

Acceptance Criteria
Metric Encoding Overlay
Given the live theme map is loaded with Blast Radius enabled and a known dataset Then each theme node encodes metrics as follows: size = affected users (square-root scale, min radius 8px at 1 user, max 36px at 95th percentile, hard cap 48px); color = ARR at risk (5 quantized buckets: $0, $1–$999, $1k–$9.9k, $10k–$99k, $100k+ mapped to CSS classes arr-0..arr-4); opacity = confidence (levels 0.25, 0.5, 0.75, 1.0 for <50%, 50–69%, 70–84%, ≥85%) And a badge stack displays exact values for users (e.g., 1.2k), ARR (localized currency, e.g., $12.3k), velocity (items/day, e.g., 24/day), and confidence (e.g., 86%) using consistent rounding rules And encodings remain stable and consistent across pan/zoom and data refresh And nodes with zero ARR display badge "$0" and use the lowest ARR color bucket
Hover Tooltips and Keyboard Focus
Given a visible theme node When the user hovers the node for ≥100ms or focuses it via keyboard Then a tooltip appears within 150ms containing the theme title and all four metrics plus last-updated timestamp And the tooltip is positioned within the viewport, follows cursor/focus, and dismisses within 200ms on mouseout/blur/Escape And the tooltip content is keyboard navigable and announced by screen readers (aria-live="polite") And hover/focus does not change node layout and maintains ≥55 FPS during interaction
Click-Through to Evidence
Given a visible theme node When the user clicks the node or selects "View evidence" in the tooltip Then an evidence drawer opens without page reload within 800ms (P95) showing items filtered to that theme and current time/window context And the evidence count matches the count shown on the node/tooltip And closing the drawer returns focus to the originating node and preserves map zoom/pan And a copyable deep link reproduces the same evidence filters when opened in a new tab
Real-Time Updates Without Reload
Given the map is open and connected to the backend stream When new feedback updates a theme’s metrics Then the node’s size, color, opacity, and badges update within 5 seconds end-to-end without a full-page reload And updates are batched to no more than once per 250ms per node to avoid jank And current viewport, zoom level, and selection are preserved across updates And a connection-loss indicator appears within 5 seconds of stream disruption and clears on reconnection
Accessibility and Contrast Compliance
Given default and dark modes Then text in badges meets WCAG 2.2 AA contrast ≥4.5:1 against badge backgrounds And non-text graphical elements (node fill vs background) meet ≥3:1 contrast per WCAG 1.4.11 And color is not the sole means of conveying information; badges provide numeric values for all encoded metrics And all interactive nodes are keyboard reachable with visible focus indicators (≥3:1 contrast) and appropriate roles/labels And tooltips are accessible via keyboard and screen reader with descriptive labels for users, ARR, velocity, and confidence
Performance and Scale Budgets
Given a dataset of 5,000 themes with 250,000 aggregated evidence items on a laptop-class device (4-core CPU, 8GB RAM, 1080p, latest Chrome) Then initial overlay render completes ≤2.0s (P95) And pan/zoom interactions sustain ≥55 FPS (P95) with no main-thread task >50ms (P95) And hover tooltip latency ≤150ms (P95); click-to-drawer open ≤800ms (P95) And overlay memory footprint ≤350MB (P95) and no crashes or unresponsive prompts occur And CPU usage returns to <15% within 1s after burst updates
Clustering and Layout Integrity
Given clustered nodes at zoomed-out levels Then cluster metrics aggregate as: affected users = sum; ARR at risk = sum; velocity = sum; confidence = weighted mean by evidence count (rounded to nearest percent) And expanding/collapsing a cluster preserves relative positions with jitter ≤10px and prevents label overlap And transitions animate within 300ms and maintain contrast/accessibility standards And collision avoidance ensures no two rendered nodes overlap at the same zoom; if density exceeds threshold, nodes are replaced by count indicators until further zoom
Stakeholder Alerts and One-Click Sync
"As an incident coordinator, I want threshold alerts and prefilled Jira/Linear issues so that I can notify stakeholders and create tasks in minutes."
Description

Provide configurable, threshold-based Slack and email alerts and augment one-click Jira/Linear sync with prefilled titles, severity, ARR at risk, impacted segments, and confidence. Include deep links back to EchoLens, attach evidence snippets, and support deduplication to avoid alert fatigue. Track sync status, external issue keys, and auto-update linked issues when blast radius changes.

Acceptance Criteria
Threshold-Based Slack Alert: ARR at Risk Exceeds Limit
Given a configured Slack destination and an alert rule with ARR at risk threshold T and confidence ≥ C When a theme’s computed ARR at risk crosses T with confidence ≥ C and the blast radius is recalculated Then a Slack message is delivered to the configured channel within 60 seconds containing: theme title, severity, ARR at risk, affected users, impacted segments, velocity, confidence, a deep link to EchoLens, and top 3 evidence snippets with sources And the alert is logged with a unique alert ID and timestamp
Email Alert: Impacted Users Threshold with Quiet Hours
Given email recipients and a rule "affected users ≥ U" with quiet hours 22:00–07:00 local When the threshold is met during quiet hours Then no immediate email is sent And a single digest email containing all qualifying themes is sent at 07:05 including: theme title, severity, ARR at risk, affected users, impacted segments, velocity, confidence, deep link, and top 3 evidence snippets And an audit entry records suppression and digest delivery with timestamps
Deduplication and Re-Alert Escalation
Given dedup window W=2 hours for a theme When repeated recalculations meet the same rule within W without a ≥20% increase in ARR at risk or affected users Then no additional alerts are sent (Slack or email) And when ARR at risk increases by ≥20% or severity increases, a new alert is sent marked "escalation" and references the previous alert ID And the total alert count per theme per 24h does not exceed N (configurable), with overflow rolled into a digest
One-Click Jira Sync with Prefilled Fields
Given a user clicks "Create in Jira" on a theme with an authorized Jira connection and selected project Then a Jira issue is created within 10 seconds with: summary "[EchoLens] <theme title> — Severity <S>", description including ARR at risk, affected users, impacted segments, velocity, confidence, deep link, top 5 evidence snippets, and EchoLens alert ID And fields map: Priority from severity, Labels include "echolens","blast-radius", Component equals mapped product area when available And the created issue key is stored in EchoLens and displayed on the theme card with a deep link to the Jira issue
Linear Sync with Permission Fallback
Given a user clicks "Create in Linear" without required workspace/project permissions When the sync is attempted Then the action fails gracefully within 5 seconds with a clear remediation message and a link to connect/authorize And no duplicate issues are created in Linear And upon successful authorization retry, a Linear issue is created with the same prefilled content as Jira and the external key/state stored and shown on the theme card
Auto-Update Linked Issues on Blast Radius Change
Given a theme is linked to external issues (Jira/Linear) When ARR at risk changes by ≥10% or severity changes Then the external issues are updated within 2 minutes: add a comment summarizing the change, update fields (Priority for Jira; Label or Priority for Linear) when permitted, and include a deep link to the updated EchoLens theme view And updates are skipped if the external issue is in a terminal state; a comment-only update is posted instead And all update attempts, outcomes, and latencies are logged with correlation IDs
Sync Status and Health Monitoring
Given outbound alerts or sync actions occur When the status view is opened Then EchoLens displays per-action state: Pending, Sent, Failed, Synced, Updated with timestamps, destination, and external keys And failed deliveries are retried up to 3 times with exponential backoff and surfaced in a "Sync Health" view with error details And a CSV export includes action type, destination, result, attempt count, total latency, and error message (if any)
Explainability and Audit Trail
"As an executive stakeholder, I want transparent explanations and an audit trail so that I can trust the numbers and share defensible updates."
Description

Expose an explain panel that shows how estimates were derived: contributing sources, deduplication steps, time windows, formulas, and model versions. Log parameter changes, thresholds, and user actions in an immutable audit trail with timestamp and actor. Enable export of a stakeholder-ready PDF/CSV snapshot and provide retention and access controls aligned with SOC 2 requirements.

Acceptance Criteria
Explain Panel Content Completeness
- Given a user views a Blast Radius estimate, When they open the Explain panel, Then it displays contributing sources by channel (reviews, emails, tickets, chats) with counts and unique IDs. - And Given the estimate required deduplication, When the panel renders, Then it shows deduplication rules applied and before/after counts. - And Given the estimate was computed over a defined period, When the panel renders, Then it shows the exact time window start/end in UTC and the data refresh timestamp. - And Given formulas are used, When the panel renders, Then it shows the formulas for affected users, ARR at risk, velocity, and confidence with variables resolved to values. - And Given a model produced the estimate, When the panel renders, Then it shows model name, version, training date, and any feature flags affecting the output. - Then all displayed values reconcile exactly to the on-screen estimate (tolerance = 0). - And Then the Explain panel is keyboard-navigable, with aria-labels, and loads within 2s for estimates derived from ≤100k events.
Explain Panel Consistency with Filters and Time Windows
- Given filters (segments, time window, thresholds) are applied, When the Explain panel is opened or settings change, Then the panel updates to reflect current parameters within 2 seconds and displays the active parameters. - Then recomputing the estimate from the panel’s sources and formulas reproduces the displayed values exactly (tolerance = 0). - And Then a parameter-change event is written to the audit trail with actor, timestamp (UTC ISO 8601), parameter name, old value, new value, and request/session ID.
Immutable Audit Trail for Parameters and User Actions
- Given any parameter change, threshold update, model selection, export, or push to Jira/Linear, When the action occurs, Then an audit log entry is appended with timestamp (UTC ISO 8601), actor (user ID/email), action type, object, object ID, old/new values, request ID, IP, and model version. - Then audit storage is append-only; direct update/delete attempts return 403 and are themselves logged. - And Then log integrity can be verified via an integrity endpoint that returns a checksum over a requested time range; altering any entry changes the checksum. - And Then audit logs are queryable by time range, actor, action type, and object, returning within 3s for up to 1,000,000 entries.
Role-Based Access and SOC 2-aligned Controls
- Given roles Admin, ComplianceViewer, and ProductUser, When accessing the Explain panel, Then all authenticated users can view it, but only Admin/ComplianceViewer can view/export audit logs or modify retention settings. - Then all access to Explain, audit log views, exports, and retention pages is logged with actor and purpose (user-entered reason required for exports). - And Then Admin/ComplianceViewer access requires MFA; insufficient privilege attempts return 403 and are logged. - And Then least-privilege is enforced: ProductUser cannot view sensitive fields in logs; emails are masked unless unredacted by Admin.
Stakeholder-Ready Snapshot Export (PDF/CSV)
- Given a user is viewing a Blast Radius estimate, When they request an export, Then both PDF and CSV are generated including: report title, timestamp (UTC), active filters, time window, impacted segments, affected users, ARR at risk, velocity, confidence, sources summary, dedup summary, formulas, model version, and data refresh time. - Then the CSV has deterministic headers and types; the PDF is paginated and legible on A4/Letter; both include a SHA-256 checksum in metadata. - And Then files are available within 5s for reports ≤10k rows; download links expire after 15 minutes; files are encrypted in transit and at rest. - And Then an audit entry records the export with file checksums and requester.
Retention Policy Configuration and Enforcement
- Given default retention is 365 days, When an Admin sets retention between 30 and 730 days, Then the setting is saved, versioned, and logged with actor, old/new value, and timestamp. - Then logs older than the active retention are auto-deleted daily; deletions are summarized and logged (count and range). - And Then legal holds can be applied and removed by Admin; items on hold are not deleted until hold removal, which is audited. - And Then only Admin can change retention/holds; changes require confirmation; querying verifies no logs older than retention (without hold) exist.

Escalation Ladder

Configurable on-call routing with quiet hours, dedup windows, and auto-escalation if unacknowledged or unresolved. Ensures critical spikes are never missed while minimizing unnecessary noise.

Requirements

On-call Schedules & Quiet Hours
"As a CX lead, I want reliable on-call schedules with quiet hours so that critical spikes are covered without waking the team for noise."
Description

Provide team- and user-level on-call schedules with time zone support, rotations, and handoffs, plus configurable quiet hours and holiday overrides that automatically defer non-critical alerts. Schedules must map to EchoLens teams and product areas so spikes from specific themes route to the correct duty roster. Quiet hours are enforceable at user, team, and global levels, with exceptions for critical severity. The system should validate coverage gaps, surface warnings for uncovered hours, and support immediate activation/deactivation of schedules during incidents. All schedule state must be queryable via API and reflected in the live theme map alerting context.

Acceptance Criteria
Time-zone aware rotating on-call schedule with DST handling
Given team T is set to timezone America/Los_Angeles with a weekly rotation [A, B] and rotation boundary at 09:00 local Monday When the date crosses DST start or end Then the active on-call at any timestamp is computed using local time rules with no overlap or gap > 0 minutes And at 09:00 local Monday responsibility switches to the next assignee with at most one switch notification When a user views the schedule in UTC Then shift start/end times are correctly converted and the computed assignee is unchanged
Quiet hours enforcement at user, team, and global scope with critical exception
Given global quiet hours 22:00–07:00, team quiet hours 21:00–06:00, and user U quiet hours 20:00–08:00 When a non-critical alert targets team T at 21:30 team-local time and U is current on-call Then the alert is deferred When a critical alert targets T during any quiet-hour window Then it is delivered immediately Then scope precedence is user over team over global, and the effective quiet-hour window is visible via API and UI
Holiday overrides defer non-critical alerts and auto-resume
Given a holiday override for team T from 2025-12-24T00:00 to 2025-12-26T23:59 in T’s local time When non-critical alerts arrive in that window Then they are deferred and recorded with suppression reason "holiday" When the window ends Then deferred alerts are released within 5 minutes in FIFO order unless resolved When critical alerts arrive during the window Then they bypass suppression And the override appears in API schedule state queries with start, end, and scope
Theme-to-product-area mapping routes spikes to correct roster
Given product area "Billing" is mapped to team T’s schedule When a spike is detected for themes tagged with "Billing" Then the notification routes to T’s current on-call assignee When no schedule mapping exists for a product area Then the system flags "No coverage" within 30 seconds in the UI and API for that area and does not drop the alert And the live theme map displays the assignee and whether delivery is immediate or deferred by quiet hours
Coverage gap detection and proactive warnings
Given a proposed schedule for team T with gaps > 0 minutes between shifts in the next 30 days When the schedule is saved Then the system blocks save with a validation error listing each uncovered interval When an active schedule becomes uncovered due to deactivation or time off Then a warning banner and API status "uncovered" appear within 60 seconds Then coverage completeness over the next 7 days is ≥ 99.9% or a warning persists
Immediate activation/deactivation during an incident
Given an incident is active in EchoLens When an admin toggles team T’s schedule to Active Then routing decisions reflect the change within 10 seconds and the live theme map shows Active status When the admin toggles T’s schedule to Inactive Then alerts targeting T are marked "uncovered" within 10 seconds and the live theme map shows Inactive status
Schedule state queryable via API and reflected in live theme map
Given a request for effective schedule state for team T or product area P at timestamp ts When the API is called Then it returns 200 with fields: onCallUserId, nextHandoffAt, timezone, quietHoursActive (boolean), holidayOverrideActive (boolean), coverageStatus (covered|uncovered), suppressionReason (if any) When the schedule or quiet hours change Then the API and live theme map reflect the change within 15 seconds Then responses are consistent across API and UI for the same timestamp
Routing Rules & Escalation Policy Builder
"As a product ops manager, I want to configure conditional escalation steps so that the right people are notified quickly based on impact and context."
Description

Deliver a visual and API-driven policy builder to define multi-step escalation ladders with conditional routing based on theme severity, confidence score, source (reviews, tickets, chats), product area, customer tier, and time of day. Each step supports targets (individuals, rotations, Slack channels, email lists), wait/ack timeouts, and fallback behavior. Policies are versioned, testable in a simulator with sample events, and attachable to queues or specific theme clusters. Integration with EchoLens ranked queue ensures the highest-impact items inherit the strictest policies, and updates propagate without downtime.

Acceptance Criteria
Multi-Step Conditional Escalation with Mixed Targets
Given a policy "Enterprise-Daytime-High" with condition: severity in [High, Critical] AND confidence >= 0.70 AND source in [tickets, chats] AND customerTier = "Enterprise" AND localTime between 09:00–17:00 When a matching event is ingested Then Step 1 notifies individual "pm_jane" within 60 seconds And if not acknowledged within 10 minutes, Step 2 pages on-call rotation "CX Primary" And if still unacknowledged after 15 additional minutes, Step 3 posts to Slack channel "#cx-escalations" And if still unacknowledged after 30 additional minutes, Step 4 emails "exec-alerts@company.com" And acknowledgment at any step cancels subsequent steps And delivery and acknowledgment timestamps are recorded on the event timeline And the applied policy id and version are stored on the event
Quiet Hours and Deduplication Window Enforcement
Given a policy with quietHours = 22:00–07:00 local and dedupWindow = 15 minutes keyed by (themeClusterId, severityBucket) When 5 matching events arrive between 22:10 and 22:20 Then only the first event triggers a notification to rotation "On-call L1" and the other 4 are suppressed And the suppression count (4) is recorded and surfaced with the triggering event And a new notification is sent for the next matching event arriving at or after 22:25 And during quiet hours Slack channel targets are replaced by the rotation target; outside quiet hours the configured primary target is used
Auto-Escalation on Unacknowledged or Unresolved Events
Given a policy step with ackTimeout = 10 minutes and resolveTimeout = 2 hours When no acknowledgment occurs within 10 minutes Then the next escalation step is executed with reason = "no_ack" And when acknowledgment occurs but the event is not marked resolved within 2 hours Then the next escalation step is executed with reason = "not_resolved" And when the event is resolved before either timeout Then escalation halts and pending steps are canceled And all executed steps are audit-logged with timestamp, target, and reason
Policy Versioning and Zero-Downtime Update Propagation
Given policy "Login-Major" version 1 is attached to queue "Critical Bugs" and themeClusterId "login_lockouts" When version 2 is published with updated targets Then events ingested after the publish time use version 2 And in-flight events escalated under version 1 continue under version 1 And no notifications are dropped and processing latency remains within 5% of baseline during publish And a rollback to version 1 completes within 1 minute and is recorded in an immutable audit log
Policy Simulator Path and Timing Validation
Given a policy with 3 steps and a sample event specifying severity, confidence, source, productArea, customerTier, and createdAt When the simulator is run Then it returns a trace showing matched condition, chosen targets per step, and absolute/relative timeout schedule And the trace includes a pass/fail outcome against user-provided expectations And invalid or incomplete sample inputs return a 400-level validation with a list of missing/invalid fields And each simulator run includes a reproducible traceId and policyVersion
Policy Management API CRUD and Validation
Given the Policy Management API When POST /policies is called with a valid policy (name, conditions, >=1 step, valid targets, non-negative timeouts) Then response is 201 Created with policyId and version = 1 And when PUT /policies/{id} is called with If-Match ETag and a valid update Then response is 200 OK with version incremented by 1 and updated ETag And when invalid fields (unknown condition key, empty targets, negative timeout) are submitted Then response is 400 Bad Request with an errors[] listing field, issue, and location And when DELETE is attempted on a policy attached to any queue or cluster Then response is 409 Conflict with references enumerated
Ranked Queue Integration and Strict Policy Inheritance
Given queue "Top Impact" auto-assigns policy "Strict Escalation" to items with impactScore >= 0.90 or rankPercentile <= 10 When an item's impactScore crosses 0.90 Then the item is assigned "Strict Escalation" within 30 seconds without interrupting any ongoing escalation And when the score drops below 0.90 Then the assignment downgrades to the default policy on the next processing cycle And theme-cluster-level policy assignments override queue-level assignments
Deduplication Window & Alert Suppression
"As an on-call engineer, I want duplicate alerts to be suppressed within a window so that I can focus on new or worsening spikes instead of noise."
Description

Enable configurable dedup windows that collapse identical or near-identical spike alerts into a single notification within a time window, keyed by theme ID, severity, confidence, and source bundle. Provide controls for per-policy dedup duration, per-channel suppression limits, and surge protection to prevent alert storms. Suppressed counts and reasons must be recorded and visible in alert details. Include overrides to bypass dedup when severity jumps or confidence crosses a threshold, and expose metrics to tune windows over time.

Acceptance Criteria
Collapse duplicates by composite key within dedup window
Given a policy with dedup_duration=15m and dedup_key=[theme_id,severity,confidence,source_bundle] And Slack is a configured notification channel When 5 spike alerts arrive for theme_id=T123, severity=High, confidence=0.87, source_bundle=Tickets within 15 minutes Then exactly 1 notification is sent to Slack And 4 alerts are suppressed with reason="dedup_window_active" And the surviving alert's details show suppressed_count=4 with suppressed_event_ids and timestamps And after the 15m window expires, the next matching alert sends a new notification
Near-identical dedup via configurable confidence tolerance
Given a policy with dedup_duration=10m and confidence_tolerance=0.03 When two alerts arrive within 10 minutes with the same theme_id, severity, source_bundle and confidences 0.82 and 0.84 (delta 0.02) Then only the first alert notifies and the second is suppressed with reason="near_identical" And when a third alert with confidence 0.89 (delta 0.07) arrives within 10 minutes, it is not deduped by tolerance and triggers a notification (unless overridden by other rules)
Per-policy dedup duration is configurable and enforceable
Given Policy A has dedup_duration=5m and Policy B has dedup_duration=20m When identical alerts (same theme_id,severity,confidence,source_bundle) are generated under each respective policy Then under Policy A, a second alert 6 minutes later sends a new notification And under Policy B, a second alert 6 minutes later is suppressed with reason="dedup_window_active" And setting dedup_duration=0 disables dedup so every alert notifies
Per-channel suppression limits cap notifications
Given Slack channel #ops has suppression_limit=3 per 10m and Email has suppression_limit=10 per 10m When 5 distinct theme spike notifications would be sent to #ops within 10 minutes Then only 3 notifications are sent to #ops and 2 are suppressed with reason="channel_limit_reached" And Email receives all 5 notifications And channel-level suppressed_count is incremented and exposed in channel metrics
Surge protection prevents alert storms with rollup
Given surge_protection is enabled with max_rate=20 alerts per 5m per workspace and rollup_interval=5m When 50 alerts are generated in a 5-minute interval Then only 20 notifications are sent and the remainder are suppressed with reason="surge_protection" And a single rollup notification is sent at the end of the interval summarizing suppressed counts by theme and channel And an audit log entry records surge_protection start_time, end_time, suppressed_count, and affected_channels
Dedup override on severity jump or confidence threshold crossing
Given a policy with dedup_duration=15m, severity_order=[Low,Medium,High,Critical], and confidence_bypass_threshold=0.90 And an initial High-severity alert with confidence=0.85 has notified When a subsequent alert for the same theme_id and source_bundle arrives within 15 minutes with severity=Critical Then it bypasses dedup and notifies immediately with reason="override_bypass:severity_jump" And when a subsequent alert arrives within 15 minutes with confidence=0.92 (>= threshold), it bypasses dedup with reason="override_bypass:confidence_threshold" and resets the dedup window from that alert
Suppressed counts and reasons visible in alert details and metrics exposed
Given an alert has suppressed duplicates during its dedup window When viewing the alert details in the UI and via GET /alerts/{id} Then fields suppressed_count (integer), suppressed_reasons (array of {reason,count}), and suppressed_event_ids (list) are present and accurate And the metrics endpoint exposes dedup_hit_rate, suppressed_count_by_reason, notifications_sent, and average_window_duration_seconds tagged by policy_id, channel, and theme_id And the dashboard displays 7/30/90-day trends for these metrics to support window tuning
Auto-Escalation on Unacknowledged/Unresolved
"As an incident coordinator, I want automatic escalation when alerts are ignored or fixes stall so that critical issues don’t slip through."
Description

Automatically escalate when alerts are not acknowledged within a defined timeout or when linked work items remain unresolved beyond SLA. Acknowledgement can be performed via Slack actions, email links, or the EchoLens UI and is tracked with user attribution and timestamps. When an alert is acknowledged but not resolved, the system monitors linked Jira/Linear issues and the live theme’s severity/volume, escalating or re-paging if SLAs are breached. Support snooze with justification, re-escalation after snooze expiry, and configurable maximum escalation depth with executive fallback.

Acceptance Criteria
Escalate on Unacknowledged Alert Timeout
Given an alert is created at T0 with ack_timeout set to 10 minutes and initial routing to Level 1 When no acknowledgement is recorded by T0 + 10 minutes Then the system sends an escalation notification to Level 2 within 60 seconds And the alert state is updated to escalated_to=Level 2 with an audit log entry including reason=unacknowledged_timeout and timestamp And Level 1 is not re-notified for this step And only one Level 2 escalation is sent per timeout event (idempotent)
Multi-Channel Acknowledgement with Attribution
Given an alert is awaiting acknowledgement and escalation timers are active When a user acknowledges via Slack action, email secure link, or the EchoLens UI Then the system records user_id, channel, and acknowledgement timestamp And the alert state changes to acknowledged and is reflected across all channels within 15 seconds And all pending scheduled escalations for this alert are cancelled And duplicate acknowledgements from any channel do not create additional audit entries or re-trigger workflows
Re-Escalation on Unresolved Work Item Breach
Given an alert has been acknowledged and is linked to a Jira/Linear issue with a resolution_SLA of 24 hours When the SLA breach time is reached and the issue status is not in a resolved/closed state Then the system triggers a re-page to the current responsible level within 5 minutes and escalates to the next level per policy if configured And the notification includes the issue key/link, time since acknowledgement, and SLA breach delta And an audit log entry is recorded with reason=sla_breach_unresolved And if the issue is resolved before the breach time, no re-page or escalation occurs
Snooze with Justification and Auto Re-Page on Expiry
Given an alert is active or acknowledged When a user snoozes the alert for a duration within the allowed range (e.g., 5–120 minutes) and provides a justification Then the system records snooze_start, snooze_end, user_id, and justification in the audit log And escalations and pages are suppressed during the snooze window And upon snooze expiry the system re-pages the current responsible level within 60 seconds And attempts to snooze without justification or beyond allowed duration are rejected with an error
Escalation Depth Capping with Executive Fallback
Given an alert has an escalation policy with levels [L1..Ln], max_depth=N, and executive_fallback configured When the alert requires escalation beyond level N due to continued unacknowledged or unresolved state Then the system sends a single notification to the executive_fallback within 60 seconds of the last level timing out And the alert is marked terminal_state=fallback_notified and no further escalations are attempted And the full escalation chain with timestamps is available in the audit log and API
Theme Spike Re-Page While Unresolved
Given an alert is acknowledged, linked to a Jira/Linear issue that remains unresolved, and associated with a live theme baseline at the time of acknowledgement When the theme’s volume increases by at least 50% versus baseline or crosses the configured critical threshold while no snooze is active Then the system triggers an immediate re-page to the current responsible level within 60 seconds And the notification includes prior baseline, current volume/severity, and spike_reason=theme_spike And an audit log entry is recorded with the metric deltas
Dedup Window for Escalations and Re-Pages
Given a dedup_window of 15 minutes is configured for escalations/pages When multiple escalation or re-page triggers occur for the same alert within the dedup window Then only the first notification is sent and subsequent ones are suppressed And each suppressed event is recorded in the audit log with reason=deduplicated and next_eligible_time And notifications resume automatically after the dedup window elapses if triggering conditions still apply
Multi-Channel Notifications & Retry Backoff
"As an on-call responder, I want alerts delivered through my preferred channels with reliable retries so that I never miss a critical spike."
Description

Provide pluggable notification channels (Slack, email, SMS, and web push) with per-user preferences, channel fallbacks, and templated messages including theme summary, confidence score, sample artifacts, and deep links to acknowledge and view the live theme map. Implement exponential backoff with jitter, idempotent delivery, and per-channel rate limits. Respect quiet hours and policy-level suppression while guaranteeing at-least-once delivery with dedup keys. Channel health is monitored, and the system automatically fails over to secondary channels on delivery errors.

Acceptance Criteria
Per-User Preferences and Quiet Hours Enforcement
Given a user has notification preferences with quiet hours and a timezone configured When an escalation notification is generated during that user's quiet hours and policy-level suppression is enabled Then no notifications are sent to that user during the quiet-hours window Given the same escalation remains active when quiet hours end When the quiet-hours window ends Then the notification is delivered via the user's highest-ranked available channel within 2 minutes using the same dedup key and is marked as delivered
Multi-Channel Fallback and Failover on Delivery Errors
Given a user's preferred channel is unavailable (e.g., provider timeout or 5xx) When the current attempt fails Then the system attempts the next preferred healthy channel within 30 seconds, preserving the same dedup key and message payload Given the original preferred channel becomes healthy When subsequent notifications are sent for that user Then delivery resumes using the user's channel preference order
Message Template Completeness and Rendering
Given a notification is generated for a theme When the message is rendered for Slack, email, SMS, and web push Then each channel-specific template includes: theme title/summary (<=200 chars), confidence score (percentage with one decimal), 1–3 sample artifacts (truncated to channel limits), and two deep links: Acknowledge and View Theme Map Given the Acknowledge link is clicked When the ack endpoint is called with a valid token Then the system records an acknowledgment for the dedup key and returns a confirmation within 2 seconds, and all pending retries/escalations for that user+key are canceled Given the View Theme Map link is clicked Then the live theme map for the referenced theme opens successfully
Exponential Backoff with Jitter
Given a transient delivery error occurs on a channel When retries are scheduled Then retry intervals follow exponential backoff with base 2 starting at 5s (5s, 10s, 20s, …) capped at 5m, with ±20% random jitter per attempt Given the cap is reached without success on a channel Then further retries on that channel stop and failover rules are applied to secondary channels
Idempotent Delivery and Deduplication
Given a notification is associated with a dedup key When multiple retries or channel failovers occur Then no more than one delivered notification per user per dedup key is recorded within the configurable dedup window (default 24h) Given the provider triggers a duplicate callback or the same request is retried When the dedup key matches an already delivered record within the window Then the attempt is discarded without user-visible duplication and an idempotent hit is logged Given at-least-once delivery is required When any delivery attempt succeeds on any channel Then all further retries and escalations for that user+dedup key are canceled
Per-Channel Rate Limiting and Queuing
Given per-channel rate limits are configured (e.g., per recipient for email/SMS, per user for Slack/web push) When a send would exceed the configured limit Then the notification is queued for that user/channel, backoff is applied, and the message is not dropped Given capacity becomes available before the message TTL When the backoff timer elapses Then the queued send proceeds; otherwise it is rescheduled until TTL expiry Given the message TTL expires before capacity is available Then the notification is marked expired with reason "rate_limited_ttl_expired" and no delivery occurs
Channel Health Monitoring and Circuit Breaker
Given a channel's rolling 5-minute error rate exceeds 20% for 5xx/timeouts or there are 10 consecutive failures When the threshold is reached Then the channel is marked unhealthy, a circuit breaker opens for 5 minutes, and traffic is routed to secondary channels Given the cool-down elapses and health checks succeed (two consecutive successes) When traffic resumes Then the channel is marked healthy and traffic is gradually restored with no more than a 20% step increase per minute
Spike Thresholds & Confidence Filters
"As a product manager, I want triggers that factor in confidence and impact so that only meaningful spikes escalate to the team."
Description

Expose configurable trigger thresholds that combine statistical spike detection (volume deltas vs. baseline) with severity tags and model confidence scores. Allow per-source weighting (tickets vs. reviews), rolling windows, minimum support counts, and customer-tier prioritization. Provide preview mode to validate policies against historical data and show expected alert frequency. Triggers integrate with the ranked queue so only high-impact, high-confidence themes generate pages, reducing noise while preserving sensitivity to true incidents.

Acceptance Criteria
Configurable Spike Thresholds on Rolling Baselines
- Given a theme stream and a selected rolling window (e.g., 7, 14, or 28 days) with baseline computed by the chosen method (mean, median, or EWMA), When the current window’s volume exceeds the baseline by at least the configured percent threshold and the absolute delta meets the configured minimum, Then the system marks a spike candidate for that theme. - Given thresholds are saved by an admin, When the policy is updated, Then new thresholds are applied to new incoming data within 60 seconds and the change is versioned with user, timestamp, and parameter diffs. - Given a theme with insufficient data, When the minimum support count N is not met for the window, Then no spike is emitted and the decision log records reason = "min_support_not_met". - Given a baseline method is selected, When Preview or runtime evaluation occurs, Then both use the same method and produce matching spike flags for identical data.
Per-Source Weighting and Minimum Support Enforcement
- Given source weights are configured (e.g., tickets=1.0, chats=0.8, emails=0.6, reviews=0.5), When calculating spike volume, Then the weighted sum = Σ(count_source × weight_source) is used for threshold comparisons and displayed in logs. - Given per-source minimum supports are configured, When a spike is evaluated, Then the spike is suppressed if any required source’s minimum is not met and the decision log lists unmet sources and thresholds. - Given weights are edited and saved, When Preview is refreshed and live processing continues, Then both use the new weights immediately and show effective weighted counts per source in their outputs.
Severity and Model Confidence Filters Gate Triggers
- Given a severity threshold (e.g., High+) and a model confidence threshold (e.g., ≥ 0.80) are configured, When a theme spike is detected, Then a trigger is emitted only if theme severity ≥ threshold AND model confidence ≥ threshold. - Given manual severity overrides are enabled, When a user promotes a theme to Critical, Then subsequent trigger evaluations use Critical immediately and the audit log records the override. - Given a confidence hysteresis of 0.05 is configured, When confidence fluctuates around the threshold, Then the trigger state does not flip more than once per dedup window.
Customer-Tier Prioritization Adjusts Trigger Sensitivity
- Given customer tiers with multipliers (e.g., Enterprise 3.0, Pro 1.5, SMB 1.0), When spike volume is computed, Then events are multiplied by tier multipliers before aggregation and the weighted-by-tier total is used for evaluation. - Given an enterprise-priority rule is enabled, When at least M Enterprise accounts report the theme within the window, Then a trigger may fire even if global volume is below the default threshold, provided Enterprise minimum support M is met. - Given a trigger is generated via tier-prioritized logic, Then the decision log includes reason = "tier_priority" and counts by tier (IDs masked).
Preview Mode Backtests and Shows Expected Alert Frequency
- Given a date range (e.g., last 90 days) and a policy draft, When Preview is run, Then the system returns within 2 seconds p95 a report including: total expected alerts, alerts/week, hourly and daily distribution, top 10 themes by projected alerts, and sample events per alert. - Given the same policy and date range, When Preview is rerun, Then results are deterministic and match prior output within 1% for counts and identical alert timestamps. - Given a threshold slider is adjusted, When Preview updates, Then the delta in expected alerts is shown inline and the parameter diff is recorded in the draft change log.
Quiet Hours and Deduplication Windows Suppress Noise
- Given quiet hours (e.g., 22:00–07:00) and a dedup window D=30 minutes, When multiple triggers for the same theme occur during quiet hours, Then no pages are sent; a single suppressed alert is recorded with aggregated count, and a consolidated notification is issued at quiet-hours end per policy. - Given a trigger fires and another arrives within D for the same theme/policy, Then only one live alert is active and its occurrence count and last_seen timestamp are updated. - Given an alert is unacknowledged beyond TTL (e.g., 10 minutes) during quiet hours, Then auto-escalation proceeds to the next rung using quiet-hours-safe channels and records the escalation step.
Ranked Queue Integration and Page Gating
- Given a trigger is emitted, When the ranked queue recomputes, Then the theme appears with an impact score = f(spike magnitude, severity, confidence, tier weighting) and rank updates within 5 seconds. - Given a page-gating impact threshold is configured, When the impact score is below the threshold, Then no page is created and the theme status is "monitoring"; when the score crosses the threshold and no open alert exists, Then exactly one page is created with context (examples, 24h trend, source breakdown) and a link to the policy version. - Given an open alert exists for the theme, When a new trigger arrives, Then no duplicate page is created; the existing alert is updated and the decision log cites reason = "open_alert_exists".
Audit Trail & Escalation Analytics
"As a head of support, I want full visibility into alert lifecycles and performance so that I can optimize policies and meet SLAs."
Description

Maintain an immutable audit trail of alert lifecycle events (triggered, suppressed, delivered, acknowledged, escalated, resolved) with actor, timestamp, reason, and policy version. Provide dashboards for MTTA, MTTR, escalation depth, suppression effectiveness, and channel performance, segmented by team, product area, and severity. Support CSV/JSON export and API access for compliance and postmortems. Insights feed back into policy recommendations to improve coverage and reduce noise over time.

Acceptance Criteria
Immutable Audit Trail for Alert Lifecycle
Given any alert lifecycle event (triggered, suppressed, delivered, acknowledged, escalated, resolved) occurs When the system processes the event Then an audit record is appended with fields: event_id (UUID), incident_id, event_type, actor_type (user/system), actor_id, timestamp_utc (ISO 8601), reason (nullable), policy_version, channel (nullable), team, product_area, severity And any attempt to update or delete an existing audit record is rejected Then the operation fails with 403 and an additional audit record is appended describing the attempted mutation And the original record remains unchanged And when verifying integrity for a given incident_id Then the audit log returns verification_status=true for an append-only sequence with no missing or altered records And when querying by incident_id with <= 10,000 events Then results are returned ordered by timestamp_utc ascending within 500 ms at p95
MTTA and MTTR Dashboard with Segmentation
Given alerts with delivered, acknowledged, and resolved events exist When a user selects a time range and filters by team, product_area, and severity Then MTTA is computed as time_between(first delivered or triggered event and first acknowledged event) And MTTR is computed as time_between(triggered event and resolved event) And values are displayed to the nearest second with p50, p90, and mean And the calculations exclude suppressed incidents by default with an option to include them And the dashboard auto-refreshes within 60 seconds of new events And all times are shown in UTC And when no acknowledgments or resolutions exist in the window Then MTTA/MTTR display as N/A and are excluded from aggregates
Escalation Depth and Timing Analytics
Given incidents that escalate across multiple steps When viewing the analytics Then each incident shows escalation_depth equal to the highest escalation level reached And time_at_each_level in seconds is displayed And aggregated charts display distribution of escalation_depth and median time_to_escalation across selected filters And incidents acknowledged before any escalation have escalation_depth = 0
Suppression Effectiveness Reporting
Given suppression due to quiet hours, dedup windows, and policies occurs When viewing suppression analytics over a selected time range and filters Then the system displays: total_alerts, suppressed_count, suppression_rate (suppressed_count/total_alerts), and breakdown by suppression_reason And clicking a suppression_reason reveals representative incidents with links to their audit trails And changing filters updates suppression_rate within 500 ms at p95
Channel Performance Analytics
Given delivery and acknowledgment events per channel exist (email, Slack, SMS, webhook) When viewing channel performance by selected filters Then the system displays for each channel: delivery_success_rate, acknowledgment_rate, median_time_to_ack, and failure_count with error reasons And channels with zero traffic are displayed as 0 with an explanatory tooltip, not omitted And clicking a channel filters the incident list to matching audit records
CSV/JSON Export and API Access
Given a user has permission to export When exporting audit logs or analytics for a selected time range and filters Then CSV exports conform to RFC 4180 with a header row and UTF-8 encoding And JSON exports are newline-delimited JSON And both include fields: event_id, incident_id, event_type, actor_type, actor_id, timestamp_utc, reason, policy_version, channel, team, product_area, severity And exports include an immutable checksum and the filter parameters used And files are available within 60 seconds for datasets up to 1,000,000 rows And via API, authenticated requests with OAuth 2.0 bearer tokens support filtering (time range, team, product_area, severity, channel, event_type) and pagination (cursor) Then responses return HTTP 200 with schema versioning metadata And unauthorized requests return 401
Insights-Driven Policy Recommendations
Given at least 14 days of analytics data When the insights engine runs nightly Then it generates policy recommendations (e.g., adjust quiet hours, dedup window, channel routing) with a rationale citing metrics (suppression_rate, MTTA, escalation_depth) and estimated impact And each recommendation can be applied or dismissed And applying updates the policy, increments policy_version, and appends an audit record with actor, timestamp_utc, old_value, new_value, and rationale_id And accepted recommendations produce a tracking card showing post-change impact after 7 and 30 days

Spike Replay

Time-lapse timeline showing how the spike formed versus baseline, with representative messages and segment overlays. Aids quick validation, stakeholder updates, and postmortems.

Requirements

Time-lapse Spike Timeline
"As a product manager, I want to see an animated timeline of a spike versus baseline so that I can quickly understand how the spike formed and communicate it to stakeholders."
Description

Deliver an interactive, scrubbable timeline that animates the formation of a spike against its computed baseline for any selected theme. The view supports play/pause controls, step-through frames, and tooltips showing absolute/relative deltas, confidence scores, and notable inflection points. Anomaly intervals are visually highlighted, with markers for significant events (e.g., release tags) when present. The timeline opens contextually from the Live Theme Map and Theme Detail pages with the same filters and time window, and it persists user selections across navigation. The component supports keyboard navigation, accessible color palettes, and responsive layouts, and it can be embedded within the theme panel without full-page navigation.

Acceptance Criteria
Contextual Open, State Persistence, and Embedding
Given the user is on Live Theme Map or Theme Detail with theme T, filters F, and time window W, When they open the Spike Timeline, Then the timeline initializes with T, F, and W applied. Given the Spike Timeline is opened from the theme panel, When it renders, Then it appears embedded without full-page navigation. Given the user adjusts timeline settings (e.g., toggles anomaly overlay, sets zoom range Z, selects frame index k), When they navigate to the other source view and reopen the timeline within the same session, Then the previously chosen settings (F, W, Z, overlay state, and k) are restored. Given the user closes the timeline, When they return to the originating view, Then original filters F and W remain unchanged.
Baseline vs Observed Visualization and Animation
Given a selected theme with computed baseline B(t) and observed counts O(t), When the timeline loads, Then both B(t) and O(t) are rendered on the same time axis with a shared scale. Given playback is started, When frames advance, Then O(t) animates in chronological order and B(t) remains visible as the baseline reference. Given a specific time bin t_i, When the frame is at t_i, Then the rendered values match the underlying data within ±1 count for O(t_i) and ±1 count for B(t_i). Given a zoom range Z is applied, When animating, Then only time bins within Z are rendered and the baseline and observed series remain aligned.
Scrub, Play/Pause, and Frame-Step Controls
Given the timeline is loaded, When the user drags the scrubber, Then the visible frame updates to the corresponding time bin and the time label reflects the bin’s start/end. Given playback is paused, When the user presses Play, Then frames advance sequentially; When pressing Pause, Then advancement stops at the current frame. Given the user clicks Next Frame or presses Right Arrow at frame k, Then the frame advances to k+1 (or stays at last frame if at end); Given Previous Frame or Left Arrow at frame k, Then it moves to k−1 (or stays at first frame). Given playback is active, When the user moves the scrubber, Then playback position seeks to the scrubbed frame and resumes from there.
Tooltips with Deltas, Confidence, and Inflection Points
Given the pointer hovers or focus lands on a data point at time t_i, When the tooltip appears, Then it shows: observed count O(t_i), baseline B(t_i), absolute delta Δ = O−B, relative delta r = Δ/B as a percentage (rounded to 1 decimal), and confidence score c (rounded to 1 decimal). Given a point is labeled as an inflection, When the tooltip is shown, Then it includes an Inflection badge and the direction (up or down). Given the tooltip is displayed near viewport edges, When it would overflow, Then it repositions to remain fully visible. Given keyboard focus is on a point, When showing the tooltip, Then content matches the hover tooltip.
Anomaly Highlighting and Event Markers
Given anomaly intervals A = [t_start, t_end], When the timeline renders, Then the intervals are highlighted with a distinct style perceivable without color alone. Given release tags or significant events with timestamps exist in the window, When the timeline renders, Then markers appear at the correct timestamps with a legend entry. Given the user clicks or focuses an event marker, When details appear, Then they show event name, timestamp, and source. Given no anomalies or events exist in the window, When the timeline renders, Then no highlights or markers are shown.
Keyboard Navigation and Accessibility Compliance
Given the component receives focus, When using Tab/Shift+Tab, Then all interactive controls (play/pause, scrubber, step, zoom, overlays) are reachable in a logical order and have visible focus indicators. Given a screen reader is in use, When controls are focused, Then each control exposes an accessible name, role, and state, and the scrubber announces the current time bin. Given the user presses Space or Enter on Play/Pause, Then playback toggles; Left/Right steps one frame; Esc closes the timeline if opened as a modal overlay. Given the UI colors, When evaluated, Then text and essential lines/markers meet WCAG 2.1 AA contrast (≥4.5:1), and anomaly highlighting uses a non-color cue (e.g., pattern or border).
Responsive Layout Behavior
Given viewport width ≤ 480px, When rendering, Then controls stack vertically, tap targets are ≥44×44 px, axes labels abbreviate, and tooltip content wraps without overflow. Given viewport width between 481 and 1024px, When rendering, Then controls fit on one row with wrapping, and the chart maintains at least 240px height. Given viewport width ≥ 1025px, When rendering, Then controls and legend align horizontally, and the chart scales to fill available width without clipping. Given the component is embedded in a narrow theme panel (≥320px width), When rendered, Then it remains fully usable without horizontal scrolling.
Baseline & Spike Detection Engine
"As a CX lead, I want accurate baseline and spike detection so that Spike Replay highlights real anomalies instead of noisy fluctuations."
Description

Implement a service that computes robust baselines and detects spikes for clustered themes using seasonality-aware rolling windows (e.g., hour-of-day/day-of-week), outlier-resistant statistics, and configurable thresholds. The engine outputs anomaly intervals with confidence scores, effect sizes, and start/peak/end timestamps, and supports backfill and re-computation when new data arrives. It provides segment-aware baselines (e.g., platform, plan, version) to improve precision, exposes tunable sensitivity per workspace, and stores results in a queryable store optimized for the timeline. All calculations integrate with existing clustering outputs and respect data retention policies.

Acceptance Criteria
Seasonality-Aware Baseline Computation
Given a 6-week dataset with hour-of-day and day-of-week seasonality for a theme When baselines are computed Then the baseline for each hour×day cell uses at least the last 4 comparable periods with a rolling window And the held-out last-week mean absolute percentage error per cell is <= 10% And baseline calculations exclude periods marked as maintenance/blackout windows
Outlier-Resistant Statistics and Tunable Sensitivity
Given a dataset where 1% of observations are 10× magnitude outliers When baselines are computed with outlier-resistant statistics enabled Then the baseline shift versus an outlier-free baseline is <= 2% And spike detection thresholds are configurable per workspace (Low/Medium/High) And on a stationary control stream, Medium sensitivity yields a false positive rate <= 5% and FPR increases monotonically from Low to High
Spike Detection Output Completeness
Given a theme time series with an injected spike lasting 3 consecutive buckets When detection runs Then a single anomaly interval is emitted with start, peak, and end timestamps each within +/- 1 bucket of ground truth And the payload includes effect_size = (peak - baseline) / max(baseline, 1e-6) And the payload includes confidence in [0,1] and confidence >= 0.8 for the injected spike
Segment-Aware Baselines With Fallback
Given events segmented by platform, plan, and version When baselines are computed Then per-segment baselines are produced for segments with >= 50 observations across the last 8 comparable periods And segments below threshold fall back hierarchically (version -> platform -> global) without error And emitted anomalies include the segment identifiers used for computation
Backfill and Incremental Re-computation
Given historical backfill for the past 30 days and late-arriving events for the last 2 hours When the engine ingests these records Then only affected windows and intervals are recomputed (no full reprocess) And prior results are versioned with a deterministic run_id And updated baselines and anomalies are queryable within 2 minutes of ingestion completion And no duplicate anomaly intervals exist for the same cluster_id and time range
Integration With Clustering and Data Retention Compliance
Given clustering outputs provide stable cluster_id per theme and a retention policy of 180 days When detection runs Then all results are keyed by cluster_id and workspace_id And results older than 180 days are purged and excluded from future calculations And deleting a cluster removes its stored results within 24 hours And reclustering with an ID remap updates stored results using the provided mapping without data loss
Queryable Store Optimized for Timeline
Given a timeline query for a theme over the last 30 days at 1-hour granularity When fetching baselines and anomalies with segment and confidence filters Then results are returned in chronological buckets with no gaps or overlaps And the 95th percentile latency is <= 200 ms (cached) and <= 700 ms (cold) at 10 concurrent users And pagination is supported with stable cursors and deterministic ordering
Representative Message Snippets
"As a product manager, I want to view representative user messages for each spike phase so that I can validate the cause quickly without reading every ticket."
Description

Surface a rotating set of representative messages for each phase of the spike (pre-spike, ramp-up, peak, resolution) alongside the timeline. Sampling balances recency and diversity, deduplicates near-duplicates, highlights key phrases and cluster exemplars, and annotates each message with channel, segment tags, and confidence indicators. Clicking a snippet deep-links to the source (reviews, emails, tickets, chats) with proper permissions. Built-in PII redaction and optional summarization produce a concise, safe-to-share view. Admins can pin or exclude examples and export a curated set for stakeholder updates.

Acceptance Criteria
Phase-specific snippet rotation and deduplication
Given a spike with messages distributed across phases pre-spike, ramp-up, peak, and resolution When the Spike Replay view loads Then show 4 representative snippets per phase by default, or all if fewer than 4 exist And ensure at least 1 snippet originates from the most recent 25% of the phase timeframe and at least 1 from the earliest 25% when available And ensure no more than 2 snippets share the same channel within a phase when alternatives exist And remove near-duplicates where semantic similarity >= 0.90 within a phase And refresh the unpinned rotation every 60 seconds or upon new message ingestion without page reload And render snippets for all phases within 2 seconds at p95
Metadata, key phrases, and confidence indicators
Given representative snippets are displayed When viewing any snippet Then display channel icon and name, up to 3 segment tags with "+N" overflow, phase label, and timestamp in workspace timezone And highlight the top 2 key phrases per snippet And show a cluster exemplar badge on exemplar messages And show a confidence indicator as a 0–100% value with tooltip explaining the score And expose aria-labels for all badges and indicators for screen readers
Deep-linking to source with permission enforcement
Given a user with Source.Read permission When the user clicks a snippet Then open the exact source message in its native system in a new tab and return HTTP 200 And include the messageId and projectId in the deep link And record an analytics event click.snippet with messageId Given a user without Source.Read permission When the user clicks a snippet Then present a modal indicating insufficient access with a "Request access" action And prevent navigation to the source And record an analytics event click.snippet_blocked with messageId Given the source message has been deleted When the user clicks a snippet Then show a non-blocking toast "Source unavailable" and keep the user in EchoLens
PII redaction across UI, summary, and export
Given snippets contain PII such as emails, phone numbers, credit card numbers, SSNs, and postal addresses When snippets are rendered, summarized, or exported Then mask detected PII with tokens [EMAIL], [PHONE], [CARD], [SSN], [ADDRESS] And achieve 0 unmasked PII occurrences in a test set of 1,000 known-PII samples And do not provide a "show PII" option to non-admin users And apply the same masking in exported files and shared links
Optional summarization of snippets
Given the "Summarize snippets" toggle is on When snippets are displayed Then show a single-sentence summary per snippet not exceeding 220 characters And preserve sentiment and core intent compared to the full (redacted) text with cosine similarity >= 0.80 on sentence embeddings And provide a "View full text" control that expands inline within 150 ms And generate summaries within 800 ms at p95 Given the "Summarize snippets" toggle is off When snippets are displayed Then show the full redacted text Given a snippet has fewer than 15 words When summarization is enabled Then bypass summarization and show the full redacted text
Admin curation: pin and exclude snippets
Given an admin user When the admin pins snippets in a phase Then allow up to 3 pinned snippets per phase And display pinned snippets first in fixed order across refreshes and sessions And persist pins for the spike's lifetime unless unpinned Given an admin user When the admin excludes a snippet Then remove it from rotation for all users immediately And prevent it from re-appearing unless reinstated And record audit entries for pin, unpin, exclude, and reinstate actions with actor, timestamp, and messageId And enforce RBAC so non-admins cannot pin or exclude
Export curated snippets for stakeholder updates
Given a set of selected or pinned snippets When the user exports to PDF or CSV Then include message text (redacted), channel, segment tags, phase, timestamp, confidence, exemplar badge, and pinned status And preserve the user-selected order And generate the file within 5 seconds at p95 And apply the workspace timezone and date format And include a header with spike name and timeframe And ensure the number of exported snippets equals the number selected
Segment Overlays & Filters
"As a CX lead, I want to overlay and filter by user segments on the spike timeline so that I can pinpoint which cohorts are driving the spike."
Description

Enable cohort overlays and filters (e.g., plan tier, platform, app version, region) on the spike timeline to visualize which segments drive volume above baseline. Users can toggle stacked or comparative overlays, multi-select segments, and persist selections in the URL state. Legends, tooltips, and contribution metrics update in real time across the timeline, snippets, and theme metadata. The feature integrates with EchoLens’s existing segment taxonomy, gracefully handles missing attributes, and supports default workspace-specific segment sets.

Acceptance Criteria
Toggle Stacked vs Comparative Overlays on Spike Timeline
Given the Spike Replay timeline is loaded with a selected theme and baseline visible When the user toggles Overlay Mode to "Stacked" Then all selected segment series render stacked by volume above baseline on a single area chart and the legend indicates "Stacked" Given Overlay Mode is "Stacked" with N selected segments When the user switches to "Comparative" Then the same N series render as separate comparative lines/areas on the same axes without changing the time range or filters Given a toggle between modes When the redraw occurs Then the y-axis scale updates appropriately and total spike volume remains equal between modes (sum of segments in Stacked equals combined volume shown in Comparative within ±0.1%) Given a mode change When the user copies the page URL Then the mode parameter is present (mode=stacked|comparative) and restoring that URL reproduces the mode Given a dataset of 10k points per series When the mode is toggled Then the visual update completes within 500 ms on a standard workstation
Multi-Select Segment Filters Persist in URL State
Given no segment parameters are in the URL When the user selects multiple segments across dimensions (e.g., Plan: Pro and Enterprise; Platform: iOS) Then the timeline, legend, snippets, and theme metadata reflect only those segments Given selected segments are applied When the user refreshes the page or opens the shared URL in a new browser Then the selections are restored from the URL and match exactly (IDs, order, and mode) Given selected segments in any order When the user presses browser Back or Forward after changing selections Then the UI state updates to match the URL history Given a clear-all action is invoked When the user deselects all segments Then the URL parameters for segments are removed and the UI reverts to the default segment set Given large segment lists When the user searches and selects up to 20 segments Then the selection is allowed and validated; attempting to select a 21st shows a non-blocking limit message and is not applied
Real-Time Legends, Tooltips, and Contribution Metrics Update
Given the user hovers a time bin in the timeline with segments selected When the tooltip appears Then it shows for each visible segment: segment label, count above baseline, percent contribution to the spike at that bin, and confidence (if available) Given the user changes selected segments or overlay mode When the timeline re-renders Then legend totals and contribution metrics update within 200 ms and match the tooltip values at the hovered bin within ±0.1% Given new data is ingested that affects the current time range When Spike Replay auto-refreshes Then legend totals, tooltips, snippets panel counts, and theme metadata KPIs update within 2 seconds and remain internally consistent Given a segment is hidden via legend toggle When the user hovers or exports metrics Then contribution percentages recompute excluding hidden segments and totals sum to 100% ±0.1%
Graceful Handling of Missing or Unknown Segment Attributes
Given events or messages lack a segment attribute (e.g., app version unknown) When overlays are computed Then those records are grouped under "Unknown: <Attribute>" as a selectable segment Given Unknown segments exist When the user views the legend and tooltips Then Unknown entries display counts and contribution percentages and are included in totals Given Unknown segments have zero volume When the UI renders Then Unknown entries are hidden by default but become visible if they gain non-zero volume or are explicitly searched/selected Given data includes missing attributes When filtering by a different segment dimension Then missing values do not cause errors; charts render and API responses return HTTP 200 with Unknown buckets represented
Default Workspace Segment Set Applied on First Load
Given the workspace has a configured default segment set When the Spike Replay timeline loads without segment URL parameters Then those defaults are applied and visible in the picker, legend, and overlays Given no workspace default is configured When the page loads without URL parameters Then a system default minimal set is applied and a non-intrusive notice offers a link to configure workspace defaults Given defaults are applied When the user modifies selections Then the current selection supersedes defaults for the session and is written to the URL Given workspace defaults are changed in settings When the user reloads the Spike Replay page without URL overrides Then the new defaults take effect
Integration with Existing Segment Taxonomy
Given the platform's segment taxonomy defines values and IDs for plan tier, platform, app version, and region When the overlay/filter picker opens Then it lists values using the same labels and internal IDs as the taxonomy Given a time range and a selected segment When counts are displayed in the legend and timeline Then totals for that segment match the backend taxonomy aggregation for the same filters within ±0.5% Given a taxonomy value is deprecated or hidden When rendering the picker Then the value is excluded unless present in the current dataset, in which case it is shown with an "inactive" badge Given hierarchical taxonomy groups (e.g., Region > Country) When selecting a parent group Then the overlay expands to allow selecting children and totals reflect the sum of selected children
Consistent Segment Context Across Timeline, Snippets, and Theme Metadata
Given a set of segments is selected When the user navigates between the Spike Replay timeline, representative snippets panel, and theme metadata sidebar Then all three surfaces reflect the same segment filters and overlay mode Given a segment is deselected from the timeline legend When the snippets list and theme metadata refresh Then entries and metrics for that segment are hidden and totals recompute consistently across surfaces Given the user switches themes within Spike Replay while retaining URL parameters When the new theme loads Then the segment selection and overlay mode persist and apply to the new theme's data Given any surface shows counts or percentages When compared for the same time bin and selection Then values match across surfaces within ±0.1% and no stale values are present
One-click Sync & Permalink Sharing
"As a product manager, I want to share a Spike Replay snapshot and open a tracking ticket in one click so that stakeholders can act immediately with the right context."
Description

Provide a shareable, permission-aware permalink that captures the Spike Replay state (theme, time window, filters) and a one-click sync to Jira/Linear that attaches a snapshot summary (baseline vs spike metrics, top segments, and representative snippets). Shared links avoid embedding PII in URLs, honor workspace access controls, and can render as a read-only snapshot to prevent drift. Optional exports (PNG for timeline, text summary for snippets) facilitate stakeholder updates, and back-links from tickets return to the exact replay state.

Acceptance Criteria
Generate permission-aware permalink capturing Spike Replay state
Given a user is viewing a Spike Replay for Theme X with time window T and filters F When the user clicks "Copy Permalink" Then a permalink is generated within 2 seconds And the permalink resolves to a read-only snapshot of Theme X with identical time window T and filters F And baseline vs spike metrics in the snapshot match the current view values exactly And representative messages and segment overlays in the snapshot match the current view And the snapshot displays a visible "Snapshot as of <timestamp>" label And interactive controls that modify state are disabled (read-only), with Share and Export available And the snapshot content remains unchanged even if underlying data changes later (no drift)
Open shared permalink with authorized access
Given a valid Spike Replay permalink belonging to Workspace W And the visitor is signed in with Viewer or higher role in Workspace W When the visitor opens the permalink Then the snapshot loads successfully within 3 seconds And the exact theme, time window, and filters are restored And the UI is read-only (no editing, no filter changes)
Access control enforcement for unauthorized or signed-out users
Given a valid Spike Replay permalink belonging to Workspace W When a signed-in user without access to Workspace W opens the link Then the system returns HTTP 403 and renders an "Access denied" view without revealing any snapshot data And the page title, URL, and metadata contain no theme names, customer identifiers, or content When a signed-out visitor opens the link Then the system returns HTTP 401 and prompts for sign-in without revealing any snapshot data
Permalink URL contains no PII or sensitive context
Given the system generates permalinks for Spike Replay snapshots When inspecting the permalink URL and query parameters Then no customer identifiers, email addresses, message content, or theme names appear in the URL And the link uses an opaque token no longer than 256 characters And decoding the token (URL/base64) does not reveal PII or snapshot content And the server resolves the token to the snapshot state on the backend
One-click sync to Jira/Linear with snapshot summary and back-link
Given a user is viewing a Spike Replay snapshot When the user clicks "Sync to Jira" or "Sync to Linear" and selects a destination project/team Then an issue is created within 5 seconds And the issue title includes the theme name and date range And the description includes baseline vs spike metrics, top 3 segments, and 3–5 representative snippets And sensitive fields are redacted according to the user's permissions And the issue description includes a back-link URL to the exact snapshot And the app confirms success with the created issue key/ID and link And on failure, the user sees a clear error and no partial issue is created
Back-link in created ticket restores exact replay snapshot
Given a ticket created via one-click sync contains the snapshot back-link When an authorized Workspace W user opens the back-link from Jira/Linear Then EchoLens opens the exact Spike Replay snapshot with matching theme, time window, filters, and snapshot timestamp And if the user lacks access, an "Access denied" view is shown with no data leakage
Export PNG timeline and text summary from snapshot
Given a user is viewing a Spike Replay snapshot When the user selects Export PNG for the timeline Then a PNG downloads within 3 seconds at a minimum resolution of 1600x900 and matches the visible timeline And the filename includes theme, date range, and snapshot timestamp When the user selects Export Text Summary Then a UTF-8 .txt downloads within 3 seconds containing baseline vs spike metrics, top segments, and representative snippets And exported files contain no PII beyond what is visible to the current user role
Real-time Updates & Performance
"As an on-call PM, I want Spike Replay to load quickly and update in near real time so that I can triage incidents without delays."
Description

Ensure Spike Replay remains live and responsive under load by pre-aggregating counts, caching anomaly computations, and streaming incremental updates. Target sub-300 ms query latency for common windows (24h, 7d) and 60 fps animation on modern browsers, with graceful fallbacks (skeleton loading and reduced-frame playback) on slower clients. The system scales to high-ingestion workspaces, supports backpressure to prevent UI jank, and exposes observability (latency, error rate, cache hit) with alerting. The UI displays a live indicator and last-updated timestamp for transparency.

Acceptance Criteria
p95 Query Latency for 24h and 7d Windows
- For Spike Replay API requests scoped to 24h and 7d windows, p95 server response time <= 300 ms over 10,000 requests during peak hour traffic with warm cache. - Cold-cache p95 server response time <= 600 ms for the same query mix; warm-up completes within 5 minutes after deploy or cache flush. - End-to-end time from user action (Play or Scrub) to first timeline paint <= 500 ms p95 for 24h/7d windows. - Results must match a correctness oracle within ±0.5% of counts versus a full recomputation for sampled queries.
60 FPS Replay on Modern Browsers with Graceful Degradation
- On latest Chrome, Firefox, Safari, and Edge on reference hardware (≥ 4-core CPU, 8 GB RAM, 2019+), Spike Replay animation sustains ≥ 60 FPS average with dropped frames ratio < 5% over a 60-second replay (p95 across test runs). - User input latency (click, hover, scrub) during playback <= 100 ms p95. - If measured FPS drops below 45 for > 1 second or main-thread budget exceeds 16.7 ms p95, the player automatically switches to reduced-frame mode (30 FPS target) within 300 ms and displays skeleton loading for heavy segments. - In reduced-frame mode, average FPS ≥ 28 and no “Unresponsive page” warnings are observed across the test matrix.
Pre-aggregation and Anomaly Cache Effectiveness
- ≥ 90% of Spike Replay queries for 24h/7d windows resolve from pre-aggregated stores (verified via tracing and metrics) during steady-state. - Anomaly computation cache hit ratio ≥ 80% for identical parameter queries within a 15-minute observation window. - Cache invalidation propagates within ≤ 5 seconds after new data arrival that affects the selected time window, and recomputed results differ by ≤ 0.5% from uncached ground truth. - Cache TTLs, hit/miss, and invalidation events are exported as metrics and visible in dashboards.
Streaming Incremental Updates and State Consistency
- Newly ingested events impacting the active spike appear in the replay UI within ≤ 2 seconds end-to-end p95 (ingest to on-screen update). - Incremental updates apply without full reload; no duplicate or out-of-order points visible to the user (verified via idempotent keys and sequence checks). - Timeline, theme map, and representative messages remain consistent; cross-component divergence window ≤ 1 second. - On transient network loss, client reconnects within ≤ 5 seconds and backfills missed updates exactly-once (no gaps, no duplicates).
Backpressure and Jank Prevention Under Burst Load
- Under a synthetic burst of ≥ 500 updates/second for 10 minutes, main-thread long tasks (> 50 ms) ≤ 2 per minute and input latency ≤ 100 ms p95. - Update coalescing/backpressure caps render application to ≤ 15 visual updates/second under burst while preserving final state correctness. - Frame time p95 ≤ 16.7 ms in steady-state and ≤ 33 ms while backpressure is active. - Client memory usage remains bounded: RSS increases by < 100 MB over a 10-minute burst and returns to within 20 MB of baseline within 2 minutes after burst ends.
Observability Metrics and Alerting for Replay Path
- Metrics emitted: replay_query_latency_ms, replay_error_rate, replay_cache_hit_ratio, stream_e2e_latency_ms, dropped_frames_ratio, backpressure_activations, client_fps_avg. - Dashboards display per-workspace and global views for the above metrics with 1-minute resolution and 30-day retention. - Alerts fire to on-call channel with runbook link when: p95 replay_query_latency_ms > 300 ms for 5 consecutive minutes; replay_error_rate > 1% for 5 minutes; replay_cache_hit_ratio < 70% for 10 minutes; p95 stream_e2e_latency_ms > 2000 ms for 5 minutes; dropped_frames_ratio > 10% for 10 minutes. - Synthetic probes run every 5 minutes and report Pass/Fail for API latency and streaming path health.
Live Indicator and Last-Updated Timestamp UX
- A live indicator is visible during active streaming, switches to Paused within ≤ 500 ms when playback or stream is paused/disconnected, and is accessible (aria-live="polite", contrast ratio ≥ 4.5:1). - Last-updated timestamp refreshes at least every 5 seconds during streaming and on each applied batch; displays in the user’s timezone with relative time tooltip and absolute time on hover. - Timestamp reflects the most recent applied event time within ≤ 1 second discrepancy and persists across navigation and page refresh. - Localization supports at least en-US and en-GB formats; QA verifies correct DST handling on boundary days.
Privacy, Compliance & Access Control
"As a security-conscious admin, I want Spike Replay to protect sensitive data and respect roles so that sharing insights does not expose PII."
Description

Enforce workspace-level access controls, role-based visibility for message content, and automatic redaction of PII across snippets, exports, and shared links. Snapshots and permalinks exclude sensitive data unless explicitly permitted, and all access to Spike Replay is audit-logged with timestamps and user identifiers. Data at rest and in transit remains encrypted, URLs carry opaque identifiers, and retention policies are applied to derived artifacts. The implementation aligns with existing SOC 2 controls and provides administrative toggles to disable message bodies in shared contexts.

Acceptance Criteria
Workspace Access Enforcement for Spike Replay
Given a user is not authenticated, When they request any Spike Replay page or API, Then the system returns 401 Unauthorized and no content or metadata is exposed, and an audit event access_denied is recorded with timestamp, ip, and resourceId. Given a user is authenticated but not a member of workspace W, When they request any Spike Replay resource belonging to W, Then the system returns 403 Forbidden with no content or metadata and logs access_denied with userId, workspaceId, resourceId, ip, and timestamp. Given a user U is a member of workspace W, When U tries to access a Spike Replay resource that belongs to workspace X ≠ W by manipulating identifiers, Then the system returns 404 Not Found and no cross-tenant data is exposed, and the attempt is audit-logged. Given a user is removed or disabled from workspace W, When the user attempts to access any Spike Replay resource, Then access is revoked within 5 minutes of deactivation (SLA) and requests return 401/403, and the denial is audit-logged.
Role-Based Message Visibility Inside Workspace
Given a Viewer role user within workspace W, When viewing a Spike Replay timeline, Then message bodies are masked and only metadata (source, timestamp, theme, confidence) is visible, and API responses omit message_body or return a redacted placeholder. Given an Analyst/Editor role with the View Message Bodies permission, When viewing a Spike Replay and the workspace-level Disable message bodies policy is OFF, Then full message bodies are visible in UI and API responses, subject to PII redaction. Given a user's role or permissions change, When the user refreshes the page or makes a new API call, Then message body visibility updates within 60 seconds to reflect the new role/permissions. Given an unauthorized client adds message_body fields to an API query, When the server processes the request, Then those fields are omitted from the response or the request is rejected with 403.
Admin Toggle: Disable Message Bodies in Shared Contexts
Given a workspace admin turns ON Disable message bodies in shared contexts, When any user creates or accesses a snapshot, export, or shared link, Then message bodies are omitted or replaced with [body-disabled-by-admin] regardless of creator role, and a policy banner/label is present in UI and exports. Given the admin turns ON the toggle, When existing snapshots and shared links are accessed, Then message bodies cease to display within 5 minutes of the change and accesses are audit-logged with policy_reason. Given the admin turns OFF the toggle, When a permitted user creates a new snapshot or shared link and explicitly selects Include message bodies, Then message bodies are included (still PII-redacted) and the artifact indicates it includes message bodies.
Automatic PII Redaction Across UI, Exports, Shared Links, and Integrations
Given message content contains PII (emails, phone numbers in E.164/common formats, credit card PANs that pass Luhn, IPv4/IPv6, SSN-like patterns), When content is displayed in Spike Replay UI, exported (CSV/PDF), shared via link, or synced to Jira/Linear, Then each PII token is replaced with a typed placeholder (e.g., [redacted-email]) and the raw token is not present in the artifact. Given PII redaction is enforced server-side, When client-side scripts are disabled or responses are fetched via API, Then PII remains redacted in all outputs. Given any user role or share configuration, When generating artifacts with message bodies, Then PII redaction cannot be bypassed and is always applied prior to distribution. Given a test fixture containing known PII values of each supported type, When rendering UI, generating exports, shared links, and Jira/Linear issues, Then all known PII values are redacted and unit/integration tests pass.
Snapshots and Permalinks: Sensitivity Controls, Opaque URLs, and Retention
Given a user generates a Spike Replay snapshot or permalink without explicitly selecting Include message bodies, When the artifact is created, Then it contains only aggregate data and redacted snippets, with message bodies excluded by default. Given a permitted user explicitly selects Include message bodies and policy allows it, When the artifact is created, Then message bodies are included with PII redaction and the artifact displays an includes message bodies indicator. Given a snapshot/permalink is created, When the URL is generated, Then it contains only an opaque token of at least 32 URL-safe characters with no workspace/user identifiers; altering any character invalidates access; repeated invalid token requests are rate-limited after 10 failures. Given workspace retention policy sets an expiration for shared artifacts, When the artifact reaches its expiry, Then subsequent accesses return 410 Gone within 60 seconds, the underlying stored artifact is deleted from primary storage and caches within 24 hours, and deletion is audit-logged.
Audit Logging of Spike Replay Access and Actions
Given any action on Spike Replay (view, create snapshot, create/delete shared link, export, enable/disable toggle, permission change, access denied), When the action occurs, Then an immutable audit log entry is written with timestamp (UTC ISO 8601), userId, workspaceId, resourceId, action, outcome (success/failure), ip, and userAgent. Given audit logs are append-only, When an API client attempts to modify or delete an audit log entry, Then the request is rejected (405/403) and the attempt is itself audit-logged. Given a workspace admin requests audit logs, When filtering by date range, user, action, or resource, Then the system returns complete, correctly filtered results ordered by timestamp; exports exclude message content and PII. Given an organizational retention policy of ≥365 days, When audit logs age beyond the configured retention, Then logs are purged according to policy and the purge is audit-logged.
Encryption In Transit and At Rest
Given any Spike Replay endpoint, When accessed over the network, Then only HTTPS with TLS 1.2+ is accepted, insecure protocols/ciphers (SSL, TLS 1.0/1.1) are disabled, HSTS is enabled, and HTTP requests are redirected to HTTPS or rejected. Given Spike Replay data and derived artifacts are stored, When inspecting database volumes and object storage, Then encryption at rest is enabled with provider-managed keys (e.g., KMS/AES-256) and key rotation occurs at least annually. Given exports are delivered via signed URLs, When the signed URL expires or is used beyond its allowed TTL/scope, Then access is denied (403) and the event is audit-logged. Given SOC 2 alignment is required, When reviewing security configuration and evidence, Then documentation maps encryption controls to SOC 2 criteria and evidence is available for audit.

Integrity Chain

Cryptographic hash-chaining for every cluster edit and score change, with a visible “Verified” badge and on-demand integrity checks. Builds trust across PMs and execs, deters quiet edits, and provides audit-ready proof that your prioritization history hasn’t been tampered with.

Requirements

Event Hash Chain for Edits and Scores
"As a product manager, I want each edit and score change to be cryptographically chained so that I can prove the history hasn’t been tampered with."
Description

Implement a cryptographic hash chain that links every cluster edit and score change in sequence. Each event is canonically serialized (cluster_id, event_type, payload_digest, timestamp, actor_id, previous_hash, version) and hashed with SHA-256, storing the resulting hash as the new head. Concurrency is handled via transactional locking to ensure a single linear chain per cluster. The mechanism must run synchronously on write with sub-50ms overhead and publish chain heads for downstream consumers. This provides tamper-evidence, enabling provable, ordered history for all prioritization-related actions across EchoLens.

Acceptance Criteria
Single Event Hash Linkage
Given a cluster with current head hash H0 And a new event E with fields (cluster_id, event_type in {cluster_edit, score_change}, payload_digest, timestamp in RFC3339 UTC, actor_id, previous_hash=H0, version) When E is canonically serialized in the specified field order and hashed with SHA-256 Then the stored event hash equals SHA-256(serialized E) And the event.previous_hash stored equals H0 And the cluster’s head is updated atomically to the new event hash
Deterministic Canonical Serialization
Given provided reference test vectors of event fields When each vector is serialized and hashed Then the produced byte-for-byte serialization matches the reference And the SHA-256 digests match the expected reference hashes across languages/services And two identical inputs always yield identical serialized bytes and digest
Concurrency Linearization via Transactional Locking
Given two or more concurrent writes to the same cluster When events attempt to append using the same prior head Then exactly one transaction commits first and advances the head And all competing transactions either retry with the new head or fail with a 409 ChainConflict error without creating forks And the persisted chain has a single linear sequence where each event.previous_hash equals the immediately prior head
Synchronous Write Overhead Limits
Given instrumentation around the hash-chain write path When appending at production-representative load Then the additional end-to-end latency attributable to the hash-chain step is ≤ 50 ms at p95 and ≤ 75 ms at p99 over 10k successful writes And the append operation remains synchronous (no deferred hashing)
Chain Head Publication to Downstream Consumers
Given a successful event append and head update When the transaction commits Then a chain-head message is published within 200 ms to the downstream event bus And the message contains (cluster_id, head_hash, timestamp, version) And delivery is at-least-once and ordered per cluster_id
On-Demand Integrity Verification
Given an intact chain of N events for a cluster When an integrity check is requested Then verification recomputes hashes in order from genesis to head and returns Verified=true with the current head hash When any stored event or previous_hash is tampered Then verification returns Verified=false and identifies the first invalid event index/hash
Immutability and Required Fields Enforcement
Given a persisted event in the chain When a client attempts to update or delete the event Then the system rejects the request (HTTP 405/409) and logs the attempt And only appends are permitted to modify history And actor_id and timestamp are mandatory and validated for every appended event
Immutable Audit Log Storage
"As a compliance lead, I want an immutable, exportable audit log so that I can satisfy audits and investigations with verifiable evidence."
Description

Create an append-only audit log that records every chain-linked event with its full metadata and resulting hash, enforced via database constraints and application-level guards. Persist periodic, signed snapshots to object storage with Object Lock (WORM) for additional immutability, including retention policies. Provide pagination and filtered export (JSON/CSV) by cluster, actor, date range, and event type. This log is the source of truth for investigations, compliance reviews, and cross-team verification.

Acceptance Criteria
DB and Application Append-Only Enforcement
Given a direct SQL UPDATE or DELETE against the audit_log table When it is executed by any role used by the application Then the database rejects the statement and zero rows are affected Given an INSERT into audit_log missing any required field (event_id, timestamp_utc, event_type, actor_id, prev_hash, metadata, hash) When it is executed Then the database returns a constraint violation and no row is inserted Given an attempt via any application API to alter or delete an existing audit_log record When the request is made Then the application returns an error indicating immutability and no change is persisted Given a successful audit event write through the application When the transaction commits Then exactly one new row exists with a unique event_id and all prior rows remain unchanged
Per-Event Hash Chain Integrity
Given two consecutive audit events e_{n-1} and e_n for the same workspace When the chain is validated Then e_n.prev_hash equals e_{n-1}.hash Given the canonical serialization of e_n’s fields (excluding hash) concatenated with prev_hash and hashed with SHA-256 When the hash is computed Then the result equals e_n.hash Given any modification to an earlier event’s stored values (simulated in a controlled test) When the chain validator scans from the start Then it reports a failure and identifies the index and event_id of the first broken link
Periodic Signed Snapshots with Object Lock (WORM)
Given the default configuration (snapshot_interval_minutes = 15, retention_days is configurable) When audit events are written Then a snapshot file is produced within the interval containing all new events since the prior snapshot and the current chain tip hash Given a produced snapshot stored in object storage with Object Lock Compliance mode enabled and retention_days = R When any attempt is made to overwrite or delete the object before the retention expiry Then the storage layer denies the operation and the snapshot remains unchanged Given the snapshot’s detached signature created with the configured KMS key When the signature is verified Then verification succeeds and the signer key ID matches configuration Given a request to reduce retention_days below the currently active value When applied Then the system rejects the change and retains the existing retention for all locked objects
On-Demand Integrity Verification and Verified Badge
Given a workspace and a selected date range When a user triggers “Run integrity check” Then the system validates the hash chain continuity across the range and verifies the signatures of any snapshots covering the range Given all checks pass When results are returned Then the UI displays a “Verified” badge with the verification timestamp and scope Given any check fails When results are returned Then the UI displays a failure state with the first broken event_id, reason, and recommendations, and no “Verified” badge is shown
Filtered Export and Pagination (JSON/CSV)
Given filters (cluster_id list, actor identifier, date range, event_type list) and page_size = 500 When an export is requested Then the result set includes only matching rows, ordered by timestamp_utc ascending then event_id, and is returned in pages of at most 500 records Given 1,001 matching rows and page_size = 500 When paging through results Then exactly 3 pages are returned with counts 500, 500, and 1, and a next_page_token is provided for the first two pages and omitted on the last page Given a requested format of JSON or CSV When the export is generated Then the file contains identical fields in both formats with correct escaping and a stable schema version identifier
Audit Record Completeness and Canonical Schema
Given any persisted audit_log row When the row is read Then it contains non-null values for: event_id (UUID), workspace_id, timestamp_utc (RFC3339), event_type (from the allowed set), actor_id, prev_hash, hash_algorithm (e.g., SHA-256), hash, and metadata (canonical JSON) Given an event of type “cluster_edit” or “score_change” When the row is inspected Then metadata includes before and after values for the changed fields and the target cluster_id Given the canonical JSON serialization rules (sorted keys, UTF-8, no insignificant whitespace) When metadata is re-serialized and hashed with the recorded algorithm and prev_hash Then the resulting digest equals the stored hash Given two events with identical payloads and prev_hash When inserted Then they must have distinct event_id values and produce identical hashes; duplicates can be detected by comparing hash and prev_hash pairs
Verified Badge and Integrity Status UI
"As an executive stakeholder, I want a clear visual indicator of data integrity so that I can trust the prioritization I’m reviewing."
Description

Display a visible "Verified" badge and integrity status for clusters, the ranked queue, and the theme map. The UI reflects the latest verification result from cached integrity checks, with tooltips showing last verified time, chain height, and verification scope. On detection of inconsistencies, show a warning state with a link to a detailed integrity view listing failing events and recommended remediation steps. Ensure accessibility and minimal rendering overhead by deferring heavy verification to background jobs.

Acceptance Criteria
Verified Badge Displays Across All Surfaces
Given I view a cluster card, the ranked queue header, or the theme map When the page renders using cached integrity results Then each surface displays a "Verified" badge when the last cached result is valid for its scope And the badge is not rendered when no integrity data exists for that scope And the badge state reflects exactly the cached status at render time without triggering a new verification job And the badge text and icon are consistent across all three surfaces
Integrity Tooltip Shows Required Metadata
Given a surface with a "Verified" or "Warning" badge When I hover the badge with a mouse or move focus to it via keyboard Then a tooltip appears within 200 ms And it displays last verified time in the user's locale/time zone, the chain height as an integer, and the verification scope label (Cluster, Ranked Queue, or Theme Map) And the values match the latest cached integrity record for that scope And the tooltip dismisses on blur, Escape key, or pointer leave
Warning State and Detailed Integrity View Link
Given a scope whose cached integrity result indicates inconsistency When I view that scope Then a "Warning" badge with a distinct color and icon replaces the "Verified" badge And a visible, focusable link labeled "View integrity details" is shown adjacent to the badge When I activate the link Then I am navigated to the Detailed Integrity View showing a non-empty list of failing events and at least one recommended remediation step And each failing event displays an identifier and timestamp matching the cached result And using the browser back action returns me to the originating surface and scroll position
Accessibility Compliance for Integrity UI
Given users relying on assistive technologies When interacting with badges and tooltips Then each badge has an accessible name announcing its state ("Verified" or "Integrity warning") and scope via aria-label or equivalent semantics And all interactive elements (badges, links) are reachable via keyboard in a logical tab order and operable with Enter/Space And color contrast meets WCAG 2.1 AA (>= 4.5:1 for text, >= 3:1 for non-text icons) And state changes (e.g., verified to warning, pending to verified) are announced to screen readers via an aria-live region without requiring a page reload
Performance and Non-Blocking Rendering
Given any page containing clusters, the ranked queue, or the theme map When the page renders Then no synchronous integrity verification job is started by the client And the integrity UI reads only cached results and does not block first contentful paint And the additional main-thread rendering time attributable to integrity UI is <= 10 ms at p50 and <= 30 ms at p95 on reference devices And the integrity UI performs at most one lightweight fetch per scope for cached status (response payload <= 5 KB)
Cache Freshness and On-Demand Check Status
Given a user triggers an on-demand integrity check for a scope When the background job starts Then the UI shows a "Verification pending" state for that scope within 2 seconds And when the job completes successfully, the badge updates to "Verified" with refreshed tooltip metadata within 5 seconds of the result being available And when it completes with inconsistencies, the badge switches to "Warning" and the link to the detailed view is displayed And if the cached result age exceeds 24 hours, the tooltip displays a "Stale verification" indicator until a new result is received
On-demand and Scheduled Integrity Checks
"As a security-conscious PM, I want to run and schedule integrity checks so that issues are detected early and I can share proof with stakeholders."
Description

Enable users and automation to trigger full or partial integrity verification for a cluster or workspace. Provide a background job that replays the chain from genesis to head, validates hashes and ordering, and emits a signed verification report. Support scheduled nightly checks with configurable sampling, alerts to email/Slack on failures, and an API endpoint to request and retrieve results. Include rate limiting and progress indicators for long-running verifications.

Acceptance Criteria
On-Demand Cluster Verification via UI
Given a user with Editor or higher permissions is viewing a cluster detail page, When they click "Verify integrity now", Then a verification job is enqueued scoped to that cluster, And the UI shows a determinate progress bar with verified_links/total_links updating at least every 5 seconds, And progress persists across page refresh or navigation, And upon completion a signed verification report is attached to the cluster activity within 10 seconds, And if all links validate the UI shows a "Verified" badge with a timestamp; if any check fails the UI shows a failure banner with the first failing link and reason.
Workspace Full Verification and Progress Persistence
Given a workspace with multiple clusters, When a user triggers "Verify all" from workspace settings, Then the system replays each cluster’s chain from genesis to head in deterministic order, And uses at most the configured max_concurrent_verifications per workspace, And shows overall progress (%) and per-cluster status (queued|running|completed|failed), And if a worker restarts mid-run the job resumes without losing verified progress, And the run produces a workspace-level summary report linking to each cluster’s signed report.
Signed Verification Report and Public Key Validation
Given a completed verification, When the signed report is retrieved via UI or API, Then the report includes scope, time range, total_links_checked, hash_algorithm, head_hash, started_at, completed_at, signer_id, result (pass|fail), and first_failing_link (if any), And the payload is signed with the platform private key and includes a detached signature, And when validated using the published public key endpoint the signature verification succeeds and the checksum matches the payload, And reports are immutable and versioned; each re-run generates a new report ID.
Scheduled Nightly Verification with Configurable Sampling
Given a workspace has schedule time set to 02:00 in its configured timezone and sampling set to 25%, When the nightly scheduler runs, Then 25% of clusters are selected using a stable seeded sampler and enqueued within 5 minutes, And if sampling is 100% all clusters are enqueued, And the schedule produces a completion summary accessible from schedule history with links to signed reports, And the scheduler prevents duplicate jobs for the same cluster within the same run.
Failure Alerts to Email and Slack on Integrity Mismatch
Given email and Slack alert destinations are configured, When a verification detects a hash mismatch, ordering violation, or tampered entry, Then an alert is sent to all destinations within 2 minutes containing workspace_id, scope, verification_id, first_failing_link_index, reason, and a link to the report, And no more than one alert is emitted per failing verification, And alert delivery failures are retried for up to 10 minutes with exponential backoff.
Verification API: Request, Status Polling, and Retrieval
Given an API client with a valid token, When POST /api/v1/integrity/verify is called with scope=cluster and cluster_id or scope=workspace, Then the server returns 202 Accepted with verification_id and poll_url, And GET {poll_url} returns status (queued|running|completed|failed), progress in [0..1], and links to the signed report on completion, And an Idempotency-Key header deduplicates identical requests for 10 minutes returning the original verification_id, And unauthorized requests return 401, forbidden scope returns 403, and unknown verification IDs return 404.
Rate Limiting and Concurrency Enforcement
Given workspace-level rate limits are configured for active verifications and hourly requests, When on-demand UI or API requests exceed these limits, Then the action is rejected with HTTP 429 and a Retry-After header (API) or an in-app message with next-available time (UI), And scheduled jobs are queued but do not start until capacity is available, And limit events are logged with workspace_id, user_id/client_id, limit_type, and reset_time.
Key Management and External Anchoring
"As a workspace admin, I want managed keys and optional external anchoring so that integrity proofs remain verifiable and trustworthy beyond EchoLens."
Description

Use tenant-scoped HMAC keys stored in a cloud KMS to sign chain links, recording key IDs and validity windows with support for rotation and overlap during cutover. Provide administrative controls for rotation, emergency revoke, and audit of key usage. Optionally anchor chain heads periodically to an external transparency log or public blockchain, recording anchors for independent verification even outside EchoLens. All secrets remain in KMS; no plaintext keys are logged or exported.

Acceptance Criteria
HMAC Signing with Tenant-Scoped KMS Key
Given a tenant has an active KMS-stored HMAC key version with validity window [start, end] And a chain link payload is ready to persist When the link is created or its score is updated Then the service computes the payload hash and requests an HMAC from KMS using the active keyId And persists the link with signature (base64), keyId, keyValidityStart, keyValidityEnd, and signatureCreatedAt within [start, end] And a KMS verify operation over the persisted payload hash and signature returns valid And if KMS sign or verify fails, the link is not persisted and the API returns a structured error with no unsigned data saved
Key Rotation with Overlapping Validity Window
Given an existing active key K1 and a new key K2 are scheduled with an overlap window O minutes When rotation is scheduled to start at T0 Then links created before T0 are signed with K1 and verify with K1 And links created at or after T0 are signed with K2 and verify with K2 And during [T0−O, T0+O], both K1 and K2 are permitted for verification according to each link’s recorded keyId And an on-demand integrity check over a chain spanning T0 verifies all links and reports chain status "valid"
Emergency Key Revocation and Signing Block
Given an administrator triggers emergency revoke for key Kx When the revoke action is confirmed Then the system marks Kx as Revoked and removes signing permissions for Kx from the signing service immediately And subsequent signing attempts with Kx fail with a clear error and no links are persisted And verification of historical links signed with Kx still succeeds using KMS verify with read-only permission And the revoke event is recorded in the audit log with actor, keyId, reason, timestamp, and tenantId
Administrative Rotation Controls and Audit Trail
Given a user with role TenantSecurityAdmin When they request a key rotation with parameters overlapMinutes and scheduleTime via UI or API Then the system creates a new tenant-scoped KMS key version and schedules activation at scheduleTime And records an immutable audit entry containing actor, oldKeyId, newKeyId, overlapMinutes, scheduleTime, and outcome And only users with TenantSecurityAdmin can perform this action; unauthorized requests return 403 and no KMS changes occur
External Anchoring of Chain Heads
Given external anchoring is enabled with backend set to TransparencyLog or PublicBlockchain and interval P minutes When P minutes elapse since the last anchor or the chain head changes (whichever occurs first) Then the system publishes an anchor containing the current chain head hash and required metadata to the selected backend And receives an immutable anchor reference (e.g., logIndex or txId) and persists it with timestamp and backend type And an anchor verification job confirms the reference maps to the same chain head hash And on transient publish failure, the system retries with exponential backoff up to 5 attempts and alerts if exhausted
Independent External Verification Without KMS Access
Given an anchor record exists for chain head H with reference R on the configured public backend When an external verifier exports the chain head proof bundle from EchoLens Then they can recompute H from exported link hashes and verify that R on the backend commits to H And the verification succeeds without access to any KMS secrets or service accounts And tampering with any persisted link causes the recomputed H to differ and verification to fail
No Plaintext Key Exposure in Logs and Exports
Given logging, metrics, exports, backups, and support bundles are enabled When operations involving keys occur (create, rotate, sign, verify, revoke, anchor) Then no plaintext key material, KMS key bytes, or HMAC secrets are logged, exported, or stored outside KMS And security scans of logs and artifacts using pattern and entropy detection report zero findings for key material And only non-sensitive identifiers (e.g., keyId) may appear; no data sufficient to derive the key is present
Backfill and Performance Safeguards
"As an SRE, I want safe backfill and performance safeguards so that we can enable Integrity Chain without disrupting normal operations."
Description

Backfill hash chains for historical events without modifying original content by deterministically serializing and hashing existing records in order. Execute as a throttled, resumable batch with metrics on throughput, latency impact, and error rates. Add circuit breakers and a durable queue so writes proceed if the hasher is temporarily unavailable, with catch-up processing. Provide observability (dashboards, alerts) for hashing duration, chain divergence, and verification failures.

Acceptance Criteria
Deterministic Backfill Hashing Without Data Mutation
Given a snapshot of N historical records and serialization schema v1 When the backfill is executed twice with order (created_at ASC, id ASC) Then for each record i in [1..N] the computed hash value hi is identical across runs And the final chain head hash is identical across runs And no write operations occur on source records (0 mutations logged)
Throttled Backfill Enforces Configured Rate and Limits Latency Impact
Given throttle is set to 500 records/min and CPU budget to 40% When backfill runs on a dataset of ≥10,000 records under normal production load Then the observed processing rate over any 1-minute window is ≤ 525 records/min And p95 API write latency increase is ≤ 10% versus pre-backfill baseline And backfill pauses when CPU exceeds 40% for ≥ 10s and resumes automatically once below threshold
Resumable Backfill With Idempotent Re-runs
Given the backfill has processed K records with checkpoints every 1,000 records When the job is terminated and restarted Then processing resumes at record K+1 without rehashing records 1..K And re-running the full backfill on the same snapshot yields an identical chain head And partial runs do not create orphan links or gaps in the chain
Circuit Breaker Protects Live Writes When Hasher Unavailable
Given the hasher error rate is ≥ 20% over a 1-minute window or timeouts ≥ 5s When the threshold is exceeded Then the circuit breaker opens within ≤ 5s and stops calling the hasher And incoming writes are accepted and enqueued durably with p95 write latency increase ≤ 5% And after two consecutive healthy windows, the breaker closes and hashing resumes
Durable Queue Preserves Order and Guarantees Catch-up
Given the hasher is unavailable for T minutes creating backlog B When the hasher recovers Then the system drains the queue at ≥ configured R records/min until backlog age ≤ 1 minute And per-cluster event order is preserved end-to-end And duplicate enqueued items (if any) are de-duplicated so each event is hashed exactly once And produced = processed + dedup_count (no message loss)
On-Demand Integrity Verification Detects Divergence
Given an admin requests an integrity check for a cluster over range [t1, t2] When the check executes Then the system recomputes and compares links, returning status "verified" if all links match And on divergence it returns the first divergent link with expected vs actual hash and affected record IDs And a verification_failure metric is incremented and an alert is emitted within ≤ 1 minute
Observability Dashboards and Alerts for Hashing and Integrity
Given normal and failure operating conditions When viewing dashboards and alerting configuration Then metrics are available for throughput, hashing duration (p50/p95/p99), queue depth, backlog age, chain divergence count, verification failures, circuit breaker state, and resource utilization And alerts exist with thresholds: p95 hashing duration > 2s for 5m, divergence count > 0, verification failure > 0, backlog age > 10m, breaker open > 5m And alert notifications are delivered to the configured channel(s)

Reason Codes

Structured, mandatory reason fields with a curated taxonomy and templates that link edits to releases, incidents, or experiments. Speeds debates by making intent explicit, improves searchability and filtering, and helps new teammates understand why changes were made.

Requirements

Reason Taxonomy Manager
"As a product ops admin, I want to manage a curated taxonomy of reason codes so that teams capture consistent, searchable intent across changes."
Description

Provide an admin-managed, hierarchical taxonomy of reason codes (categories, subcategories, and tags) with versioning, deprecation, and change logs. Support import/export (JSON/CSV), localization of labels/descriptions, and guardrails (ownership, approvals) to ensure consistency across teams. Taxonomy updates propagate instantly to capture forms, search filters, API responses, and exports without breaking historical data (backward-compatible mapping). Enables standardized, searchable intent across edits and decisions in EchoLens.

Acceptance Criteria
Create and Edit Hierarchical Taxonomy
Given I am an admin with taxonomy.manage permission When I create a category, a subcategory under it, and a tag under that subcategory, each with a unique code and localized labels/descriptions Then the system persists a three-level hierarchy (category > subcategory > tag), enforces code uniqueness within the taxonomy, and returns 201 Created with node IDs and codes And codes are immutable after creation; subsequent edits allow only labels/descriptions and metadata And attempting to create a node that would exceed three levels is rejected with 400 and a validation message
Versioning, Deprecation, and Backward-Compatible Mapping
Given a published taxonomy version v1 When I publish changes that alter structure (add/remove/move nodes) or deprecate a node Then the system creates a new version with semantic versioning (major on structural changes/deprecations, minor on label/description changes) And deprecating a node requires selecting one or more replacement mappings or marking as retired with rationale And historical records referencing deprecated codes remain intact and queryable; analytics and APIs resolve both original_code and current_mapped_code in responses And attempting to publish deprecations without mappings (except retired) is blocked with 409 and a descriptive error
Instant Propagation to Forms, Filters, APIs, and Exports
Given a taxonomy draft is approved and published When the publication completes Then capture forms, search filters, and API endpoints reflect the new taxonomy within ≤5 seconds And the /taxonomy and /reasons endpoints return the new version with updated ETag/version headers And exports generated after publication include a taxonomy_version field and show current labels while preserving original codes for historical rows And clients requesting an older version via versioned API routes continue to receive vN responses without error
Import/Export (JSON/CSV) with Validation and Round-Trip Integrity
Given I upload a taxonomy file in JSON or CSV conforming to the published schema When I run a dry-run import Then the system validates and returns a report with per-row errors/warnings including row/column numbers; no data is changed And When I run a real import with the same file and zero errors Then the system creates a draft taxonomy matching the file and produces a diff summary And exporting that draft to JSON and CSV produces files that, when re-imported, recreate the same draft without loss of codes, IDs, mappings, versions, or localizations
Localization of Labels and Descriptions
Given locales en-US (default) and fr-FR are enabled When I provide translations for any node Then the system stores locale-specific labels/descriptions; missing translations fall back to default And API and UI responses respect Accept-Language or user preference for returning labels; code and id remain constant across locales And exports include one column per locale for label and description And search supports code-based queries independent of locale and label-based queries in the active locale
Ownership, Approvals, and Change Logs
Given each taxonomy or node has an assigned owner group When a user without ownership submits changes Then the system sets status to Pending Approval and notifies owners; publishing is blocked until at least one owner approves and no owner rejects And every change (who, when, what) is recorded in an immutable changelog with a diff, stored for ≥3 years, and exportable as CSV And attempts to bypass approval via API are rejected with 403; all actions are permission-checked and auditable
Mandatory Reason Capture
"As a product manager, I want to be required to select a reason and template when making changes so that my intent is explicit and comparable across the team."
Description

Enforce structured, mandatory reason selection (with optional templated notes) whenever users perform key actions: editing or merging clusters, re-tagging themes, reprioritizing the ranked queue, or updating statuses. Provide low-friction UI (keyboard-first picker, recent selections, favorites), validation, and clear error states. Include a template library with auto-filled context tokens (entity name, theme, source, confidence, user) to make intent explicit and comparable. Apply the same rules via API/webhooks to prevent bypass. Capture both reason code and free-text rationale for downstream analytics.

Acceptance Criteria
Cluster Edit/Merge Requires Reason Capture
Given a user is editing or merging a cluster in EchoLens When they attempt to save without selecting a reason code Then the action is blocked and an inline or modal error reading "Reason is required" is displayed And the reason picker receives keyboard focus by default When the user selects a reason code and optionally enters notes Then the Save/Merge action becomes enabled And the submitted event persists reason_code, rationale (string, may be empty), template_id (nullable), user_id, entity_id, and action_type
Theme Re‑tagging Requires Reason and Notes Capture
Given a user changes a theme’s tag from Tag A to Tag B When they try to confirm without a reason code selected Then the confirm action is blocked and an inline error is shown next to the reason field When a valid reason code is selected (from taxonomy) and optional notes are provided Then the change is saved And the audit payload includes reason_code, rationale, previous_tag, new_tag, user_id, entity_id, and timestamp
Queue Reprioritization and Status Updates Enforce Reasons
Given items in the ranked queue or a work item status is being updated (e.g., from Backlog to In Progress) When a user attempts to save the new rank or status without a reason code Then the action is blocked and the reason field shows an error state When a reason code is selected (and optional notes entered) Then the change is committed And for bulk operations, the selected reason applies to all selected items and the confirmation shows the item count affected And each affected item records its own reason_code and rationale in the event log
Template Library Auto‑Fills Context Tokens
Given a user opens the reason modal and selects a template containing tokens {entity_name}, {theme}, {source}, {confidence}, and {user} When the template is applied Then the notes field is auto-populated with the template text where tokens are resolved from current context values And tokens with unavailable context resolve to "N/A" And the user can edit the auto-filled notes before submission And the saved event includes template_id and the final resolved notes text
Keyboard‑First Picker with Recents and Favorites
Given the reason picker is opened When the user presses keyboard shortcut (e.g., Ctrl/Cmd+K) or focuses the field and types Then the list filters by typed text and the first item is highlighted for Enter selection And arrow keys navigate, Enter selects, and Esc closes And a Recents section shows the user’s last 5 selected reason codes, selectable via keyboard And a Favorites section pins user-starred reasons and persists across sessions
API/Webhook Enforcement and Error Contracts
Given an API or webhook request performs any protected action (cluster edit/merge, theme re-tag, reprioritize, status update) When reason_code is missing Then the request is rejected with HTTP 400 and error_code "REASON_REQUIRED" When reason_code is provided but not in the active taxonomy Then the request is rejected with HTTP 422 and error_code "REASON_INVALID" When a valid reason_code is provided and optional rationale is included Then the request succeeds and the stored event includes source="API" or source="Webhook" and mirrors UI-captured fields
Audit Trail Captures Reason and Rationale for Analytics
Given any protected action is successfully completed via UI or API When the event is recorded Then the audit store contains: reason_code, rationale, template_id, action_type, entity_type, entity_id, previous_value, new_value, user_id, timestamp, and source And the record is retrievable via the audit API within 5 seconds of action completion And reason_code and rationale fields are available to downstream analytics exports
Release/Incident/Experiment Linking
"As a CX lead, I want to link my change rationale to the relevant release, incident, or experiment so that stakeholders can trace outcomes back to the intent."
Description

Allow users to link reason entries to releases (Jira/Linear issues or GitHub releases), incidents (PagerDuty/Jira), and experiments (Optimizely/Amplitude) via searchable pickers. Fetch and display key metadata (status, owner, timestamps) and create durable backlinks where supported. Store external references with permissions-aware tokens and handle link rot gracefully. Surface link badges in the ranked queue, theme details, and change logs. Include quick-create for Jira/Linear from the capture modal and sync links bi-directionally where APIs permit.

Acceptance Criteria
Link to Releases/Incidents/Experiments via searchable picker
Given a user is adding or editing a Reason entry When they open the Link picker and type at least 3 characters Then the picker searches Jira, Linear, GitHub Releases, PagerDuty Incidents, Optimizely Experiments, and Amplitude Experiments and returns the top 10 results by relevance within 1500 ms And each result displays source icon, title/name, key/ID, status (if applicable), and owner When the user selects a result and saves the Reason Then the link is stored with source, external ID, URL, and linkedAt timestamp and is visible on the Reason entry
Fetch and display key metadata for linked items
Given a Reason entry with one or more links When the entry is viewed anywhere in the app Then each link shows status, owner, and last updated timestamp pulled from the source When metadata changes in the source system Then EchoLens updates the displayed metadata within 5 minutes or instantly on manual refresh If a metadata fetch fails Then the UI shows a stale indicator and last successful sync time without blocking the page
Create durable backlinks where supported
Given the source's API supports backlinks or comments When a link is created in EchoLens Then EchoLens creates a backlink on the external item pointing to the Reason entry with title and URL If backlink creation fails Then EchoLens retries up to 3 times with exponential backoff and logs the failure to the activity log When a link is removed in EchoLens Then EchoLens removes or marks obsolete the backlink on the external item where permitted
Permissions-aware tokens and secure storage
All OAuth tokens are stored encrypted at rest and scoped to the minimum permissions needed for reading metadata and writing backlinks Given a user without a connected integration When they attempt to link an external item Then they are prompted to authenticate; the link action proceeds only after successful auth If a token is revoked or expires Then subsequent link and sync operations fail gracefully, show an actionable error, and notify the workspace admin; existing links remain visible if public data is available
Graceful link rot handling and recovery
Given a previously valid link When the external item is deleted or the user loses access Then the link displays an Unavailable state with reason (deleted or permission), preserves last known metadata in the audit log, and offers a Repair action When access is restored or the item is recreated Then the link returns to normal state on the next sync cycle The UI never exposes raw 4xx/5xx error bodies or tokens
Surface link badges in queue, theme, and change logs
In the ranked queue, theme details, and change logs Each Reason entry with links shows badges for each source with counts Clicking a badge opens a hovercard with title, status, owner, last updated, and a deep link to the source Badges are keyboard accessible (tab focusable, Enter to open) and have aria-labels for screen readers
Quick-create Jira/Linear and bi-directional link sync
Given the Reason capture modal When the user selects Quick-create > Jira or Quick-create > Linear and provides required fields (project/team, title) Then an issue is created and linked to the Reason within 3 seconds median The created issue receives a backlink to the Reason where the API allows If the link is removed in the external system Then EchoLens removes the local link on the next sync within 10 minutes; if removed in EchoLens, the external backlink is removed as well Sync logic respects rate limits and retries with exponential backoff; failures are surfaced in the activity log
Reason-Based Search & Filters
"As a product analyst, I want to filter by reason codes and linked entities so that I can quickly find comparable decisions and their outcomes."
Description

Extend search and filtering across EchoLens to include reason codes, categories, linkage types (release/incident/experiment), authors, and time ranges. Support saved views, shareable URLs, exports, and API query parameters. Index new fields for fast retrieval and ensure filters propagate to the live theme map and dashboards. Provide empty-state guidance and chips to quickly refine results. Maintain performance SLAs under large datasets with pagination and incremental loading.

Acceptance Criteria
Faceted Reason-Based Search Across Attributes
Given a workspace contains feedback items with reason codes, categories, linkage types (release, incident, experiment), authors, and timestamps And the default facet logic is AND across facets and OR within a facet When a user applies filters: Reason Codes = ["Pricing","Onboarding"], Category = "Bug", Linkage Types = ["Incident","Release"], Author = "alex@acme.com", Time Range = Last 30 days (workspace timezone) Then only items that match all selected facets are returned and items outside the Last 30 days are excluded And the result count displayed equals the count returned by the backend And unselected facets remain available with accurate counts reflecting the current filter context And clearing any single filter immediately recomputes results and counts And boundary times are inclusive of start and exclusive of end (e.g., [start, end))
Saved Views and Shareable URLs
Given a user has an active filter set applied When the user saves it as a view named "Pricing Bugs - Last 30 Days" Then the view appears in Saved Views with the correct name, owner, and last updated timestamp And reopening the saved view restores the exact filters and sort order And a shareable URL is generated that encodes the filter state When another teammate with workspace access opens the URL in a new session Then the same results and filters are applied And if the view is deleted, the URL returns a 404 View Not Found without exposing filter details
Search API Query Parameters and Pagination
Given an API client calls GET /v1/search with query parameters reason_codes=pricing,onboarding&categories=bug&link_types=incident,release&authors=alex@acme.com&start_time=2025-07-18T00:00:00Z&end_time=2025-08-17T00:00:00Z&page_size=50&cursor=<opaque>&sort=created_at_desc Then the response status is 200 and returns JSON with fields: items[], total, page_size, next_cursor, applied_filters And items[] only contain records matching all filters with stable sort by created_at desc And page_size respects a max of 200; requests above max are clamped to 200 And when next_cursor is provided in a subsequent call, the next page of non-duplicated results is returned And p95 latency for filtered queries over 1M records is <= 1500 ms and p99 <= 2500 ms
Filtered Export to CSV and JSON
Given a user has applied any combination of filters When the user exports results as CSV Then the file includes only filtered records and columns: id, created_at, reason_code, category, linkage_type, author, title, snippet, confidence_score, theme_ids And the file uses UTF-8 encoding with RFC 4180 CSV compliance And exports up to 100,000 rows synchronously; if the filtered set exceeds 100,000 rows, an asynchronous export job is queued and the user is notified on completion with a download link that expires in 24 hours When the user exports as JSON Then the payload mirrors the Search API schema and respects the same filters
Filter Propagation to Theme Map and Dashboards
Given filters are applied in the search view When the user navigates to the Live Theme Map Then nodes, edges, and metrics reflect only the filtered subset And p95 time to reflect filter changes is <= 2000 ms When the user opens Dashboards with the same session filters Then KPIs, trend charts, and counts reflect the filtered subset and are labeled with an active-filter banner And clearing filters in any one surface (search, theme map, or dashboards) clears them across all surfaces in the session
Empty-State Guidance and Refinement Chips
Given the current filters return zero results When the results view renders Then an empty-state message explains no matches were found and displays up to five context-aware refinement chips (e.g., Clear all, Last 90 days, Remove Category: Bug, Include Experiments, Include All Authors) And clicking any chip updates filters and re-executes the search with visible feedback And an inline control allows clearing all filters with a single action And the empty state is keyboard navigable and meets WCAG 2.1 AA contrast requirements
Performance, Indexing, and Incremental Loading at Scale
Given a workspace with >= 10 million feedback items indexed on reason_code, category, linkage_type, author, created_at When the user loads the first page with default filters Then p95 time-to-first-page is <= 1500 ms and p99 <= 2500 ms And subsequent pages via incremental loading (infinite scroll or next) have p95 <= 1200 ms And the UI streams results in batches without blocking input, showing a loading indicator between pages When a new feedback item with reason metadata is ingested Then it becomes searchable and filterable within p95 2 minutes and p99 5 minutes of ingestion And index updates do not degrade ongoing query latency beyond 10% at p95 during reindex operations
Immutable Audit Trail
"As a compliance-conscious admin, I want an immutable audit trail of reasons and changes so that we can demonstrate accountability and meet audit requirements."
Description

Record an append-only audit entry for every change with reason code, template used, free-text notes, linked entities, actor, timestamp, and affected objects. Implement tamper-evident hashing and role-based access controls, plus export (CSV/JSON) and retention settings to meet compliance needs. Provide a human-readable timeline with diffs and deep links back to themes, queue items, and external systems. Expose a read-only audit API for SOC2-friendly evidence collection.

Acceptance Criteria
Append-Only Audit Entry on Change with Required Fields
Given an authorized editor modifies a trackable object (theme, queue item) and provides a reason code and template When the change is saved Then exactly one new audit entry is appended (no in-place update) capturing non-null actor_id, actor_role, timestamp (UTC ISO 8601 ms), object_type, object_id, change_type, reason_code_id, reason_template_id, notes (may be empty string), linked_entity_refs[], and affected_objects[] And GET /audit?object_id={id} returns the new entry as the last item ordered by timestamp ascending And any attempt to PUT/PATCH/DELETE an audit entry returns 405 Method Not Allowed And the audit entry persists across system restarts and is visible within 2 seconds of the change being saved
Tamper-Evident Hash Chain Verification
Given a new audit entry is created for an object scope When the entry is persisted Then it includes content_hash = SHA-256 over a canonical JSON of the entry (UTF-8, sorted keys, excluding content_hash and previous_hash) and previous_hash referencing the prior entry in that scope (or null for the first) And calling GET /audit/verify?object_id={id} returns 200 with verification=true when no entries were altered And if any stored field in a prior entry is modified, GET /audit/verify?object_id={id} returns 409 with verification=false and the index of the first failing entry And exporting and re-verifying the same sequence reproduces the same hashes
Role-Based Access Controls for Audit Data
Given roles Admin, Auditor (read-only), and Contributor exist When a Contributor accesses GET /audit or the audit UI Then they can view entries for objects they have object-level access to but cannot export or access the audit API; export controls are hidden in UI and API returns 403 for /audit/export and /api/audit When an Auditor with scope audit.read authenticates Then they can list, filter, and export audit data but cannot create, update, or delete audit entries; attempts return 405/403 as appropriate When an Admin updates audit visibility settings for a project Then only assigned roles/groups can view audit entries for that project within 60 seconds of the change
Filtered Export to CSV and JSON (NDJSON)
Given a user with export permission selects a date range, actor(s), object_type(s), and reason_code(s) When they request an export in CSV Then a downloadable file is produced within 60 seconds containing only matching records, with a header row and stable column order: audit_id, timestamp, actor_id, actor_role, object_type, object_id, change_type, reason_code_id, reason_template_id, notes, linked_entity_refs, affected_objects, previous_hash, content_hash And when they request JSON export Then the response streams NDJSON where each line is a complete JSON object matching the same schema And CSV fields are UTF-8, timestamps are UTC ISO 8601 ms, arrays are JSON-encoded in a single cell And exports over 100k rows are chunked via cursor pagination with no duplicates or gaps
Retention Policy Enforcement and Observability
Given a workspace retention policy of R days is configured (90 ≤ R ≤ 3650) When an audit entry’s age exceeds R days Then it is purged within 24 hours by an automated job and excluded from queries and exports And the first retained entry in a sequence sets previous_hash=null and verification succeeds for the retained window And updates to the retention policy themselves generate audit entries capturing the old and new values and the actor
Human-Readable Timeline with Diffs and Deep Links
Given a user opens the audit timeline for a specific object When entries are rendered Then each entry displays actor display name, relative time and exact timestamp, change summary, reason code label, template name, and deep links to affected objects and external systems (e.g., Jira/Linear) And selecting “View diff” shows field-level changes with additions highlighted (+) and removals highlighted (−) for text and property changes And the timeline paginates 50 entries per page and loads the next page within 500 ms after data fetch for up to 10k entries And clicking a deep link navigates to the correct target in a new tab or in-app view
Read-Only Audit API for SOC2 Evidence
Given an API client authenticates with an API token scoped to audit.read When it calls GET /api/audit with filters (date range, object_id, actor_id, reason_code) and a cursor Then it receives 200 with a paginated, immutable list sorted by timestamp asc, including ETag and X-Audit-Signature (HMAC-SHA256 over the response body) And all write methods (POST, PUT, PATCH, DELETE) on /api/audit* routes return 405 And rate limiting enforces at least 600 requests/min per token with headers indicating remaining quota And the API returns only entries permitted by RBAC and never includes redacted fields beyond the audit schema
Reason Insights Dashboard
"As a head of product, I want insights into which reasons drive actions and outcomes so that I can prioritize investments and improve decision quality."
Description

Deliver analytics that track top reason codes over time, impact on triage speed, correlation with fix velocity, and downstream customer outcomes. Offer segmentation by team, product area, and source channel, plus anomaly alerts when specific reasons spike. Provide drill-downs to underlying changes and linked releases/incidents/experiments. Enable scheduled reports and embeddable widgets for weekly reviews.

Acceptance Criteria
Top Reason Codes Over Time Visualization
- Given the user opens the Reason Insights Dashboard with no filters, when it loads, then a time-series chart shows the top 10 reason codes ranked by weekly count over the last 12 weeks. - Given the user adjusts the date range between 7 and 365 days and selects daily/weekly/monthly granularity, when applied, then all values recompute within 2 seconds and the top-N ranking reflects the selected range. - Given two reason codes tie at the Nth rank, when ranking is computed, then ties are broken alphabetically and the list still contains exactly N codes. - Given new feedback is ingested, when the dashboard is refreshed, then metrics reflect data with a freshness SLA of 15 minutes or better. - Given the user hovers a data point, when the tooltip appears, then it shows reason code, period start date, count, and percent of total for that period.
Triage Speed Impact Metrics
- Rule: Triage speed is defined as the time from feedback item creation to first transition into the "Triaged" state. - Given the dashboard is loaded, when viewing the Triage Speed widget, then it displays per-week median and p90 triage speed (hours) and delta vs the prior 4-week baseline. - Given a reason code is selected in the legend or filter, when applied, then the widget shows triage speed for items with that reason code and the delta vs the organization's overall triage speed for the same period. - Given at least 100 items exist in a week, when metrics are computed, then medians are displayed; otherwise a badge "Low sample size" is shown and the week is included but flagged. - Given filters for team, product area, and source channel are set, when applied, then the widget recomputes within 2 seconds.
Impact Analytics: Fix Velocity and Customer Outcomes
- Rule: Fix velocity is defined as (a) count of linked fixes shipped per week and (b) median time-to-fix (days) for items linked to a reason code. - Given at least 12 weeks of data, when computing correlation between weekly reason-code counts and fixes shipped in the subsequent 4 weeks, then the dashboard shows Pearson r and p-value; if p >= 0.05 or sample < 12, it shows "Insufficient evidence" instead of a correlation value. - Given the user selects a reason code, when viewing impact, then the dashboard shows 4-week trailing deltas versus org average for CSAT, NPS, churn rate, and ticket reopen rate attributable to items with that reason code. - Given outcome integrations are missing for any metric, when rendering, then that metric shows "Data unavailable" and does not block other metrics from rendering. - Given filters (team, product area, source channel) are applied, when metrics recompute, then both correlation and outcomes respect the filters and update within 3 seconds.
Segmentation by Team, Product Area, and Source Channel
- Given the user opens the filter panel, when selecting one or more teams, product areas, and source channels, then all widgets on the dashboard update to reflect the intersection within 1.5 seconds. - Given the user clears filters, when applied, then the dashboard returns to the unfiltered state. - Given filters are set, when the user copies the URL, then the URL encodes the filter state and rehydrating the link restores the same view. - Given the user lacks permission to some teams/product areas, when opening the filter panel, then only authorized values are visible/selectable. - Given no data matches the selected filters, when rendering, then an empty-state message "No data for selected filters" appears and suggests clearing filters.
Anomaly Alerts for Reason Spikes
- Rule: An anomaly is detected when a reason code's weekly count exceeds its 8-week rolling mean by >= 3 standard deviations OR increases >= 50% week-over-week with a baseline of >= 20 occurrences. - Given anomaly detection runs hourly, when an anomaly is detected, then an alert is created containing reason code, time window, expected vs actual, delta %, top contributing source channel, and a deep link to the filtered dashboard. - Given notification preferences are configured, when an alert is created, then a message is delivered to the selected channels (email and/or Slack) within 5 minutes; failures are retried up to 3 times and logged. - Given a user snoozes an alert for a reason code, when another spike occurs within the snooze window, then no duplicate notifications are sent. - Given filters and team scoping are configured on an alert subscription, when alerts are evaluated, then only anomalies matching the scope generate notifications.
Drill-Down to Linked Changes, Releases, Incidents, and Experiments
- Given the user clicks a reason code in a chart or table, when the drill-down opens, then it lists underlying feedback items and linked artifacts (releases/incidents/experiments) with columns: ID, title, created date, team, product area, source channel, reason code, linked type/ID, fix shipped date. - Given there are more than 50 results, when the list renders, then it paginates in pages of 50, supports sorting by any column, and loads the first page within 1.5 seconds. - Given the user clicks a linked artifact, when navigating, then it opens in a new tab in the appropriate system (Jira/Linear/incidents/experiments) with the correct deep link. - Given the user lacks permission to a linked artifact, when rendering the list, then that row is redacted and labeled "Insufficient permissions". - Given filters are applied on the dashboard, when opening drill-down, then the same filters are applied to the drill-down results and are visible as chips; the drill-down has a shareable URL.
Scheduled Reports and Embeddable Widgets for Weekly Reviews
- Given a user creates a schedule, when they specify day, time, timezone, recipients, and a saved filter set, then a weekly report is delivered within 10 minutes of the scheduled time containing the Top Reasons, Triage Speed, Impact Analytics, and Anomaly sections. - Given a scheduled delivery fails, when retries are attempted, then the system retries up to 3 times with exponential backoff and logs success/failure with timestamps. - Given a user generates an embeddable widget, when a signed URL token is created with an expiration (1–90 days) and optional disabled controls, then the embedded chart renders in under 2 seconds and honors the saved filters/date range; revoking the token immediately disables access. - Given an embedded widget is loaded, when rendering, then a "Last updated" timestamp and data freshness indicator (<= 15 minutes) are visible. - Given a user pauses or deletes a schedule, when saved, then no further deliveries occur and existing links in prior emails remain accessible for at least 90 days or until tokens are revoked.

Time Travel

Point-in-time snapshots let you view the ranked queue, confidence scores, and theme map exactly as they were on any date—then safely restore with one click. Perfect for postmortems, compliance reviews, and quickly undoing an overzealous merge.

Requirements

Immutable Snapshot Capture
"As a product manager, I want to capture an exact snapshot of the feedback analysis at a specific time so that I can later review or restore it for postmortems and audits."
Description

Implement on-demand and scheduled point-in-time snapshots that atomically capture the complete EchoLens analytical state, including ranked queue ordering, confidence scores, live theme map topology, active filters/segments, ingestion cutoffs, model and embedding version IDs, deduplication rules, user tags, and workspace configuration. Snapshots must be write-once, content-addressed, timestamped with timezone, and idempotent with retry-safe operations. Ensure referential integrity across entities and emit success/failure events for observability. This forms the source of truth for exact historical views and safe restores.

Acceptance Criteria
On-Demand Atomic Snapshot Capture
Given an active workspace with ranked queue, confidence scores, theme map, active filters/segments, ingestion cutoffs, model and embedding version IDs, deduplication rules, user tags, and workspace configuration And concurrent ingestion and clustering may be in progress When a user with Snapshot:Create permission triggers "Capture Snapshot" for that workspace Then the system creates exactly one atomic snapshot representing a single logical point in time And the snapshot includes all listed entities with complete data and consistent cross-references And the snapshot is assigned a content-addressed ID (SHA-256 digest of the serialized content) and a server-side timestamp including timezone offset And the operation returns snapshot_id, timestamp, timezone, and digest with status=success
Idempotent, Retry-Safe Capture Semantics
Given a client provides an idempotency key X for snapshot creation When the same request is retried one or more times due to network or 5xx errors Then exactly one snapshot is persisted And all successful responses for key X return the same snapshot_id and digest And no partial or duplicate snapshots exist for key X And requests without an idempotency key are rejected with 400
Scheduled Snapshot Execution with Timezone and Ingestion Cutoff
Given a workspace schedule configured as "0 2 * * *" in timezone America/New_York When the scheduled time occurs Then a snapshot is created exactly once for that schedule occurrence with timestamp at 02:00 local time including timezone offset And the snapshot uses an ingestion cutoff equal to the schedule trigger time (no records ingested after that time are included) And the operation is idempotent per schedule occurrence (retries or multiple runners produce the same snapshot_id) And failures are retried per backoff policy and emit a failure event if retries are exhausted
Referential Integrity and Deterministic Content Addressing
Given a completed snapshot When validating the snapshot manifest Then every entity reference (queue entries -> items, items -> themes, themes -> models/embeddings, tags -> items, configuration -> IDs) resolves to an object present in the snapshot And the serialized manifest ordering is deterministic And recomputing the digest over the manifest and payloads yields exactly the stored digest And altering any byte of content yields a different digest
Write-Once Immutability and Access Controls
Given a stored snapshot When any user or system attempts to modify or delete snapshot content or metadata via API or admin UI Then the system denies the request with 403 or 405 and leaves the snapshot unchanged And storage enforces write-once semantics (WORM) at the object level And all attempted mutations are recorded in an immutable audit log with actor, timestamp, and reason
Observability: Success/Failure Events and Metrics
Given a snapshot creation attempt (manual or scheduled) When the operation completes or fails Then the system emits a structured event containing snapshot_id (or correlation_id on failure), workspace_id, trigger_type (manual|scheduled), outcome (success|failure), error_code (on failure), duration_ms, object_counts by type, digest (on success), and timestamp with timezone And metrics counters and timers are updated (success_total, failure_total, duration_ms) with labels for workspace and trigger_type
Exact Historical View Reproducibility
Given a snapshot_id When the system renders the ranked queue and theme map directly from the snapshot Then the ordering of the ranked queue, confidence scores, theme map topology, active filters/segments, deduplication rules, user tags, and workspace configuration exactly match the state at capture time And comparisons against a baseline capture of the live system at that time yield zero differences
Time-indexed Snapshot Browser
"As a CX lead, I want to quickly find and preview snapshots from a given date so that I can verify what customers were seeing during an incident."
Description

Provide a UI and API to discover and retrieve snapshots by date/time, label, release, or creator. Include a calendar time picker, search, filters, pagination, and metadata display (creator, reason, version, size, retention expiration). Offer quick previews of key metrics (top themes, queue head, confidence score distribution) without full restore. Ensure fast indexing for large workspaces and consistent results across UI and API.

Acceptance Criteria
Calendar and time picker returns correct snapshots
Given a workspace with snapshots at 2025-08-16 10:15 UTC, 2025-08-16 23:59 UTC, and 2025-08-17 00:01 UTC across users When a user selects date 2025-08-16 and timezone UTC in the calendar/time picker Then the results list contains only snapshots whose timestamp falls between 2025-08-16 00:00 and 23:59 UTC inclusive, sorted by timestamp desc then snapshot_id asc And the timezone indicator is visible and switching to UTC-07:00 re-resolves the list to the local day boundary with the expected snapshot set for that zone And selecting an exact time T returns snapshots with timestamp <= T, sorted desc, and shows an empty state if none qualify And selecting a snapshot reveals a details pane without restoring or modifying workspace state
Filter and search by label, release, and creator
Given snapshots tagged with labels ["incident","release-qa"], releases ["v1.4.2","v1.4.3"], and creators ["a.lee","m.khan"] When a user applies filters label=incident and release=v1.4.2 and creator=a.lee Then only snapshots that match all selected facets are returned (AND across facets), with OR semantics within a facet for multi-select And free-text search is case-insensitive and matches substrings across label, release, and creator fields And clearing any filter immediately updates the results and count And the active filter chips reflect the applied filters and can be removed individually And the same filter set yields identical snapshot IDs via API and UI
Pagination and stable ordering
Given a workspace with 150+ snapshots When page size is set to 50 and the list is viewed Then exactly 50 snapshots are shown on pages 1 and 2, and the remainder on the last page And no snapshot appears twice across pages and no gaps occur when navigating Next/Prev And the ordering is stable and deterministic: primary sort by timestamp desc, secondary by snapshot_id asc And changing page size re-computes pages without changing the relative order of items And the UI displays the total count and current page range (e.g., “51–100 of 153”)
Snapshot metadata display accuracy
Given a snapshot is listed in the browser When the row is rendered Then it displays metadata fields creator, reason, version, size (in MB with one decimal), and retention expiration (relative time with tooltip showing exact ISO-8601 timestamp) And values exactly match backend fields for that snapshot (creator_id->display name mapping included) And missing values render as “—” with no layout shift And size is rounded half-up to one decimal and retains raw bytes in a tooltip And retention expiration counts down in real time and highlights red if < 72 hours remaining
Quick preview of key metrics without restore
Given a snapshot is selected from the list When the user opens the Quick Preview panel Then the panel loads within 700 ms p95 and never triggers a restore operation or modifies current workspace state And it shows Top Themes (top 5 by count with counts), Queue Head (top 10 items with IDs and titles), and Confidence Score Distribution (bucketed 0–0.2, 0.2–0.4, 0.4–0.6, 0.6–0.8, 0.8–1.0 summing to 100% ±0.1%) And all preview values match the snapshot’s stored metrics as returned by the preview API for that snapshot ID And errors show a non-blocking inline error with a retry action and do not crash the page
UI/API parity for listing and retrieval
Given the UI has filters {date=2025-08-16, tz=UTC, label=[incident], release=[v1.4.2], creator=[a.lee]}, sort=timestamp_desc, page=2, pageSize=50 When the List Snapshots API is called with equivalent parameters Then the API returns the same total_count, the same ordered snapshot_ids for the requested page, and identical metadata for those rows And the Retrieve Snapshot API returns metadata and preview metrics that match the UI details/preview exactly And empty result sets return HTTP 200 with an empty list and total_count=0 And invalid parameters return HTTP 400 with field-specific error messages And all timestamps are returned in ISO-8601 with timezone offsets and displayed consistently in the UI
Indexing performance and search latency at scale
Given a workspace with ≥100k snapshots and 1M+ underlying feedback items When a new snapshot is created Then it becomes discoverable via both UI and API search/filter within 30 seconds p95 (60 seconds p99) And list/filter queries over 100k snapshots have latency ≤800 ms p95 and ≤2 s p99 server-side And concurrent queries (≥20 QPS) maintain correctness with no missing or duplicated results across pages And eventual consistency windows do not exceed 30 seconds for new/updated snapshot metadata And background indexing does not block listing; partial indexes are excluded from results until complete
Consistent State Serialization
"As an engineer, I want snapshots to include configuration and model versions so that restoring yields the same results even after the system changes over time."
Description

Define a canonical, versioned schema to serialize all relevant analytical and configuration state required to faithfully reconstruct a past view. Include queue entries with exact rankings and scores, theme graph nodes/edges/weights, clustering/model parameters and IDs, feature flags, workspace settings, external link mappings, and any dataset filters. Provide deterministic ordering, checksums, and backward-compatible migrations for schema evolution to guarantee consistent rehydration across app versions.

Acceptance Criteria
Canonical Snapshot Serialization
Given a workspace with populated ranked queue, theme graph, clustering/model parameters and IDs, feature flags, workspace settings, external link mappings, and dataset filters When a Time Travel snapshot is created Then the artifact includes a schemaVersion using semantic versioning (major.minor.patch) And includes all of: queue entries with exact ranks and confidence scores, theme graph nodes/edges/weights, clustering/model parameters and IDs, feature flags, workspace settings, external link mappings, and dataset filters And serializes numeric fields with locale-invariant '.' decimal separator and fixed 6-decimal precision And orders collections deterministically: queue entries by rank asc then entryId asc; nodes by nodeId asc; edges by (sourceId asc, targetId asc) And includes a manifest with byte length per section and a global stateHash (SHA-256 of the canonical payload)
Lossless Restore Fidelity
Given a valid snapshot produced by EchoLens When it is restored into the original workspace or a test workspace Then ranked queue order and rank values match the snapshot exactly And confidence scores and theme weights differ by no more than 1e-6 And theme graph topology (node/edge sets) and weights match the snapshot And clustering/model IDs and parameters match exactly And feature flags, workspace settings, external link mappings, and dataset filters match exactly And the normalized post-restore state hash equals the snapshot's stateHash
Deterministic Round-Trip and Cross-Platform Reproducibility
Given a valid snapshot artifact When it is restored and immediately re-serialized on two different machines and OSes using the same app version Then the canonical re-serialized bytes are identical across machines and runs And the SHA-256 of the canonical payload equals the original stateHash And three consecutive restore→serialize cycles produce byte-identical canonical payloads
Backward-Compatible Schema Migration
Given snapshots with schemaVersion major equal to the app major and minor less than or equal to the app minor When restored on the newer app version Then automatic migration completes with no data loss and a migration report is recorded with fromVersion and toVersion And validation of the migrated state passes all Lossless Restore Fidelity checks And if schemaVersion major is less than the app major, a registered migration path is executed and validated as above And if schemaVersion major is greater than the app major, restoration is blocked with error E-SCHEMA-UNSUPPORTED and no state changes occur
Integrity and Checksum Enforcement
Given a snapshot with embedded per-section SHA-256 checksums and a global checksum When any checksum fails (e.g., a single byte corruption) Then restore is aborted before writing any state with error E-CHECKSUM-FAIL And an audit log entry is created with artifact ID, reason, and verifier details And the UI presents a non-destructive retry option and the artifact remains unchanged
External Link Mapping Preservation and No Side Effects
Given a snapshot containing external link mappings to Jira/Linear issues When the snapshot is restored Then all external link mappings are restored exactly with their original external IDs And no outbound writes to external systems occur during restore unless the user explicitly triggers Sync Now And if an external item no longer exists, the link is marked Stale and flagged for review without blocking restore
One-click Restore with Preview & Rollback
"As an admin, I want to restore a previous state with a clear preview and an automatic rollback option so that I can undo mistakes without risking data loss."
Description

Enable selecting a snapshot and performing a safe restore that includes a pre-restore diff preview (queue order changes, score deltas, theme map differences), impact summary, and explicit confirmation. Execute the restore atomically, capture an automatic pre-restore snapshot for rollback, and provide progress, error handling, and partial failure recovery. Include a dry-run mode and safeguards to prevent unintended external side effects unless explicitly chosen.

Acceptance Criteria
Diff Preview and Impact Summary
Given a user with Restore permission selects snapshot S dated D When they click Restore to open the preview Then the system computes and displays queue order changes (current rank vs snapshot rank for each impacted item) And displays confidence score deltas (absolute and percentage for each impacted item) And displays theme map differences (nodes/edges added, removed, or moved) And shows an impact summary with total counts of items changed, scores updated, and themes affected, plus top 5 impacted themes And generates the preview within 5 seconds for datasets up to 50,000 items And persists no changes during preview
Explicit Confirmation Gate with Opt-in Side Effects
Given the diff preview is visible When the user clicks Confirm Restore Then the user must acknowledge a confirmation statement before proceeding And external side effects (Jira, Linear, webhooks) are Off by default and require explicit opt-in via toggles And the dialog shows the number of internal entities to be changed and the count of potential external updates if opted-in And the Confirm action remains disabled until acknowledgement is provided And if no opt-ins are selected, the restore executes with zero external calls
Atomic Restore of Internal State
Given the user has confirmed restore for snapshot S When the restore runs Then the ranked queue, confidence scores, and theme map are updated in a single atomic transaction identified by Restore ID R And on any failure before commit, no internal state changes are persisted And after success, all reads reflect snapshot S consistently (read-your-writes) And a verification pass confirms post-restore state matches snapshot S for all applicable entities And an audit log entry records R, S, actor, start/end timestamps, and outcome
Automatic Pre-restore Snapshot Creation
Given a restore is initiated When execution begins Then the system first creates an immutable pre-restore snapshot P of the current state And links P to Restore ID R with metadata (initiator, timestamp, source snapshot S) And if snapshot P creation fails, the restore is aborted with no internal changes And snapshot P is visible in the Time Travel list within 5 seconds of creation
One-click Rollback to Pre-restore Snapshot
Given a completed restore with linked pre-restore snapshot P When the user selects Rollback and confirms Then the system restores state to P atomically under new Restore ID R2 And upon completion a verification pass confirms equality with P for all applicable entities And an audit log entry links R2 to the original restore R with actor, timestamps, and outcome And the rollback action is available from the same UI context as restore
Dry-run Mode with No Writes
Given the user enables Dry-run mode for snapshot S When they execute the dry-run Then the system performs validations and produces the same diff preview and impact summary as a real restore And no internal writes occur (no state changes and no pre-restore snapshot is created) And no external calls are made to integrations or webhooks And the dry-run result is clearly labeled as Dry Run and is downloadable as a report
Restore Progress, Error Handling, and Partial Failure Recovery
Given a restore is running or has encountered errors When the user opens the restore progress modal or activity log Then progress is shown by phase (pre-snapshot, validation, apply, verify, external sync) with percentage complete and ETA And transient internal errors are retried up to 3 times with exponential backoff And on unrecoverable internal failure before commit, the operation aborts, internal state remains unchanged, and a downloadable error report with error codes is provided And if external sync was opted-in and some updates fail after a successful internal restore, failed items are logged, retried idempotently up to 3 times, and a one-click Retry failed external syncs action is available And in all failure cases where internal restore completed, a Rollback option to the pre-restore snapshot is available
Role-based Access & Approvals
"As a compliance officer, I want restores to require appropriate permissions and approvals so that we meet audit and governance requirements."
Description

Introduce fine-grained permissions for snapshot operations: create, view, download, delete, restore, and manage retention. Support optional two-person approval workflows for restore actions in regulated workspaces. Enforce contextual warnings, capture approver rationale, and block actions for unauthorized users. Integrate with existing workspace roles and SSO groups to ensure compliance and least-privilege access.

Acceptance Criteria
Enforce Role-Based Permissions for Snapshot Operations
Given a user with only View Snapshot permission When they attempt to create, delete, restore, download, or manage retention Then the UI disables those controls, the API returns 403 with error_code=SNAPSHOT_FORBIDDEN, and an audit event is recorded per attempt Given a user with Create Snapshot permission When they create a snapshot Then a snapshot is created with id, timestamp, actor, and an audit event Outcome=Success Given a user with Delete Snapshot permission When they confirm deletion of an existing snapshot Then the snapshot is deleted, becomes unavailable for restore/download, and an audit event Outcome=Deleted is recorded Given a user without View Snapshot permission When they request the snapshots list via API Then the response is 404 to avoid information disclosure and an access_denied audit event is recorded Given a user with Restore Snapshot permission in a workspace where approval is not required When they initiate a restore Then the restore executes and the ranked queue, confidence scores, and theme map match the snapshot exactly, with an audit event Outcome=Restored
Two-Person Approval for Restores in Regulated Workspaces
Given a regulated workspace with restore_approval=Required and a user with Restore Request permission When the user initiates a restore Then a pending approval is created requiring a different user with Restore Approve permission to approve within the configured window Given a restore request is pending When the requester attempts to approve their own request Then the system blocks the action with 403 and shows 'A different approver is required' Given an approver with Restore Approve permission When they approve with a non-empty rationale within the approval window Then the restore executes and both request and approval events are recorded with timestamps and actors Given an approver with Restore Approve permission When they reject with a non-empty rationale Then the restore does not execute and the requester receives a notification of rejection Given a restore request exceeds the approval window without decision When time expires Then the request auto-expires, no restore occurs, and an audit event Outcome=Expired is recorded
Contextual Warnings and Impact Summary for Restore
Given a user initiates a restore When the confirmation modal appears Then it displays snapshot timestamp, affected workspace, counts of items that will be overwritten in the ranked queue and theme map, and a statement that current state will be replaced Given the confirmation modal is displayed When the user has not acknowledged the warning via required checkbox or typed confirmation Then the Restore/Submit button remains disabled Given a workspace has newer snapshots than the target When the confirmation modal is shown Then it lists the newer snapshot timestamps and clarifies they will remain available after the restore Given a user confirms the warning and proceeds When the restore (or approval request) is submitted Then the system records that the warning was acknowledged with actor, timestamp, and snapshot_id in the audit log
Capture Approver Rationale and Complete Audit Trail
Given a restore approval or rejection is submitted When the approver does not provide a rationale Then the system blocks submission and displays 'Rationale is required' Given a restore approval or rejection is submitted with rationale When the decision is saved Then the audit log stores snapshot_id, requester_id, approver_id, decision, rationale, timestamp, and workspace_id immutably Given an admin views the audit trail When they filter by snapshot_id or decision Then matching approval and restore events are returned with complete metadata Given an API client with Audit Read permission requests approvals history When they call the audit endpoint with snapshot_id Then the service returns entries including rationale with sensitive fields redacted per policy
Integration with Workspace Roles and SSO/SCIM Groups
Given workspace role-to-permission mappings are configured When a user is assigned a role Then their effective permissions reflect the mapping immediately after token refresh and within 5 minutes for active sessions Given an IdP SSO group is mapped to a workspace role When a user is added to or removed from the IdP group Then their effective permissions in EchoLens update within 5 minutes and are logged Given a user is deprovisioned via SCIM When the deprovision event is received Then all session tokens are revoked and the user loses access to snapshot operations immediately Given a new user is invited to the workspace When no explicit role is granted Then the user has no snapshot permissions by default (least privilege) Given role or group mappings change When the change is saved Then an audit event records the actor, changes, and timestamp
Retention Policy Management and Enforcement
Given a user with Manage Retention permission When they set a workspace snapshot retention to a value within 7–365 days Then the policy saves and the effective date/time are displayed and logged Given a workspace with retention=N days When the nightly retention job runs Then snapshots older than N days are permanently purged and become non-restorable and non-viewable, with audit events Outcome=Purged Given a user attempts to restore or download a purged snapshot When they call the API or use the UI Then the action is blocked with HTTP 410 Gone and a user-facing message 'Snapshot has been purged by retention policy' Given a regulated workspace has a minimum retention policy When a user attempts to set a value below the minimum Then the save is blocked with validation error and no change is persisted
Secure Snapshot Download Exports
Given a user with Download Snapshot permission When they request a download for a snapshot Then a time-bound link (TTL ≤ 10 minutes) is generated and an audit event Outcome=ExportRequested is recorded Given a download link is generated When the file is downloaded Then the package includes snapshot data plus metadata (snapshot_id, timestamp, checksum_sha256) and the checksum verifies integrity Given a user without Download Snapshot permission When they attempt to download via UI or API Then the request is denied with 403 and an audit event Outcome=Denied is recorded Given a download link has expired When a user attempts to use it Then the system responds 410 Gone and no data is returned
Storage, Retention & Cost Controls
"As a workspace admin, I want to control how long snapshots are kept and where they are stored so that we balance compliance needs with storage cost."
Description

Provide configurable retention policies by age, count, label, or legal hold; support storage tiering (hot/warm/cold), compression and block-level deduplication to minimize costs; and enable bring-your-own S3-compatible storage with KMS encryption and data residency controls. Surface storage usage dashboards, budget alerts, and automatic pruning according to policy while protecting snapshots under legal hold.

Acceptance Criteria
Policy-Based Retention by Age, Count, Label, and Legal Hold
Given retention policies are configured with age=90 days, count=200 per project, and label-based rules (label=Keep, TTL=365 days) And at least 500 snapshots exist across multiple projects with mixed labels and 20 snapshots under legal hold When the scheduled pruning job runs Then snapshots older than 90 days without label Keep and not under legal hold are deleted until count per project <= 200 And snapshots with label Keep are retained up to their label TTL (365 days) regardless of global age policy And snapshots under legal hold are never deleted And a dry-run produces counts of would-be deletions before execution And a deletion report lists snapshot IDs, policy reason, timestamp, and operator And pruning completes within 15 minutes for up to 10k snapshots
Automatic Hot/Warm/Cold Tiering with Access SLAs
Given tiering policy hot->warm after 14 days and warm->cold after 90 days is enabled And 1,000 new snapshots are created daily When 105 days have elapsed Then snapshots age into warm and cold tiers automatically according to policy with no user action And current tier is shown per snapshot in UI and API And restoring a warm-tier snapshot completes within 30 seconds for up to 5 GB logical data And restoring a cold-tier snapshot begins rehydration within 1 minute and completes within 5 minutes for up to 5 GB logical data And metadata for all snapshots remains hot for browsing and search And tier transitions generate no duplicate storage of identical blocks
Compression and Block-Level Deduplication Savings and Integrity
Given block-level deduplication and compression are enabled with default settings And a dataset of 1 TB logical data with at least 50% duplicate blocks and 2x compressible text logs is ingested as snapshots When storage savings are computed Then the platform reports logical vs physical bytes, dedup ratio, and compression ratio per tier and overall And effective physical storage is reduced by at least 40% for the dataset And restoring any snapshot yields identical checksums to source (bit-for-bit) And dedup/compression occur before tiering so savings are reflected across all tiers And savings calculations are available in the dashboard and API
Bring-Your-Own S3-Compatible Storage with KMS and Residency Controls
Given an S3-compatible endpoint, bucket name, region=eu-central-1, access credentials, and a customer-managed KMS key are configured When a connection test is run Then the test validates bucket access, region, KMS permissions, and required API compatibility And the system refuses configuration if the bucket region is not in the allowed residency list And all newly written objects are encrypted at rest using SSE-KMS with the provided key And encryption headers and object metadata confirm the KMS key ID for each object And rotating the KMS key results in new writes using the new key while existing objects remain readable And no data is written outside allowed regions; attempts are blocked and logged
Storage Usage Dashboards with Budget Thresholds and Alerts
Given tier cost rates are configured and budget limit is set to $2,000/month with a warning at 80% When usage crosses 80% of the monthly budget Then the dashboard shows current logical/physical usage by tier, dedup and compression savings, and estimated month-end spend And a warning alert is sent via email and Slack within 10 minutes including current spend, delta vs budget, and top contributing projects When usage reaches 100% of the monthly budget Then a critical alert is sent via email and Slack within 10 minutes and is recorded in the audit trail And the API exposes the same metrics and alert state
Automatic Policy-Driven Pruning on Budget Breach (Legal Holds Protected)
Given budget-based auto-pruning is enabled and retention rules are defined (oldest-first, exclude labels Keep, exclude legal holds) And the monthly budget is exceeded for 24 consecutive hours When auto-pruning executes Then snapshots are deleted in policy order until spend forecast drops below budget or no eligible snapshots remain And snapshots under legal hold or with label Keep are never deleted And a pruning report with IDs, tiers, and reclaimed bytes is published to the dashboard and sent to admins And if budget cannot be met without violating holds and labels, a failure notification is sent with recommendations
Legal Hold Management and Enforcement
Given a user with Legal Hold Manager role selects N snapshots and applies a legal hold with reason and expiration=None When the hold is applied Then the selected snapshots are immediately protected from deletion regardless of retention or budget policies And the UI and API reflect hold status, reason, applied_by, and applied_at for each snapshot And removing a legal hold requires dual-approval and a reason; all actions are audit-logged And attempting any delete on held snapshots is blocked with a 403 and a descriptive error And holds persist across tier migrations and storage backends
External Sync Integrity (Jira/Linear)
"As a product manager, I want restores to respect existing Jira and Linear links so that we avoid duplicate issues and preserve historical context."
Description

Maintain and validate mappings to external issue trackers across snapshot and restore operations. During preview, detect and flag divergences with external issues; on restore, offer options to re-open, comment-with-reference, or skip resync to avoid duplication. Ensure webhook hygiene to prevent update loops and record all cross-system actions for traceability.

Acceptance Criteria
Snapshot Preserves External Mappings
Given a ranked-queue item linked to Jira and/or Linear issues at time T0, When a snapshot is created, Then the snapshot stores for each link: system type, project ID/key, issue ID/key, status, assignee, and last webhook event ID/signature. Given external issue states change after T0, When viewing the snapshot, Then the UI shows the T0 values with an explicit as-of timestamp and does not fetch live data unless the user selects Compare to now. Given a snapshot is exported, When inspecting the metadata, Then external link attributes captured at T0 are present and checksum-verified.
Preview Flags Divergences
Given a restore preview from snapshot T0, When any linked external issue has changed (status, assignee, project, title) or been deleted since T0, Then the preview lists a divergence per item with T0 vs current values and a severity label (breaking/non-breaking). Given an external API is unreachable or permissions are insufficient, When running the preview, Then affected links are marked Unverified with the failure reason and a retry option. Given divergences are detected, When the preview completes, Then a summary count by severity is displayed and can be exported as CSV/JSON.
Restore Offers Safe Resync Options
Given divergences exist, When initiating restore, Then the user can choose per linked issue one of: Re-open, Comment with snapshot reference, or Skip resync. Given Re-open is selected, When restore runs, Then the external issue is transitioned to an open state valid for its workflow and a comment is added containing snapshot ID, actor, timestamp, and a deep link to EchoLens. Given Comment with reference is selected, When restore runs, Then only a comment with the same metadata is added and no state change occurs. Given Skip is selected, When restore runs, Then no external API call is made for that link and the result is recorded as Skipped. Given bulk selection is needed, When Apply to all is used, Then the chosen option applies to all selected items and each action is logged individually.
Duplicate Prevention During Restore
Given a restore will update linked external issues, When executing restore, Then the system must not create new issues; only update existing links according to the selected option. Given a linked external issue is missing or deleted, When restore runs, Then the item is flagged Missing and no new issue is created unless the user explicitly confirms Recreate for that item (disabled by default). Given network retries occur, When re-attempting external updates, Then idempotency keys ensure at-most-once effect per item within 24 hours. Given a potential duplicate is detected by external ID/key match or title+fingerprint in the target project, When restoring, Then the system re-links to the existing issue and avoids creating or reopening duplicates.
Webhook Loop Prevention
Given EchoLens performs an external update during restore, When the corresponding webhook event is received, Then EchoLens validates a shared secret and an EchoLens-origin idempotency key to suppress self-triggered updates and prevent loops. Given a webhook event does not carry a valid EchoLens signature, When processed, Then it is treated as third-party and handled normally without suppression. Given signature validation fails or the event cannot be correlated, When processing, Then the event is quarantined with a visible alert in the integration health dashboard and no automated follow-up actions are triggered.
End-to-End Auditability
Given any preview, restore, or external sync action, When it completes or fails, Then an immutable audit record is created with snapshot ID, item ID, external system, issue ID/key, action, parameters, actor, timestamps, outcome, HTTP status/response, and idempotency key. Given an auditor filters by date range, snapshot ID, actor, or action type, When exporting audit data, Then CSV and JSON exports are available and include a verifiable checksum. Given a user opens an item detail, When viewing history, Then a timeline shows cross-system actions with before/after field diffs and links to the external issue and the snapshot.

Delta Diff

Side-by-side comparisons that highlight confidence deltas, affected segments, ARR-at-risk shifts, and representative quotes moved between clusters. Gives PMs and CX leads a fast, visual way to validate changes and share clean before/after context with stakeholders.

Requirements

Side-by-Side Comparison UI
"As a product manager, I want to see two snapshots side by side with clear highlights so that I can quickly validate changes and explain them to stakeholders."
Description

Implements a responsive, accessible interface to display two selected snapshots (e.g., current vs. previous week or custom timestamps) in a synchronized, side-by-side layout. Highlights per-theme changes with color-coded badges for confidence delta, segment shifts, and ARR-at-risk, and supports expand/collapse to inspect representative quotes and metrics. Integrates with the live theme map to maintain consistent clustering visuals and enables quick filters (segment, product area, severity) and hover tooltips for metric definitions. Provides deep links to underlying clusters, tickets, and Jira/Linear items to preserve context and speed validation.

Acceptance Criteria
Responsive, Accessible Side-by-Side Layout
- Two snapshots render in two equal-width panes when viewport ≥1200px (each ≥560px wide); no horizontal scroll on body. - Viewport 768–1199px: panes remain side-by-side if each can be ≥420px; otherwise auto-switch to stacked view with a visible toggle to return to side-by-side. - Viewport <768px: stacked view is default with tabs to switch active pane; active tab is visually indicated. - Keyboard-only navigation reaches all controls in logical order across panes; visible focus states meet WCAG 2.1 AA. - Landmarks/regions are labeled (aria-label: "Snapshot A", "Snapshot B"); panes announced to screen readers on toggle. - Initial skeleton loaders appear within 1.5s p75; full UI interactive within 3.0s p75 on standard broadband. - No layout shift >0.1 CLS introduced by this feature during initial render.
Synchronized Selection and Scroll Across Panes and Theme Map
- Selecting a theme in left pane auto-scrolls right pane to the matching theme anchor within 20px alignment and highlights both rows for 2s. - "Sync scroll" toggle ON: vertical scrolling in one pane mirrors in the other; OFF: panes scroll independently. - Changing Snapshot A or B via timestamp picker updates only the targeted pane while preserving selection and scroll position of the other. - If a selected theme is missing in one snapshot, show a "Not present" placeholder with badges "New" (exists only in A) or "Dropped" (exists only in B). - Live theme map highlights the same cluster IDs for A and B when a theme is selected; map view updates within 200ms of selection. - URL reflects state (snapshotA, snapshotB, selectedTheme, syncScroll) and restores the same on reload/share.
Delta Badges: Confidence, Segment Shifts, ARR-at-Risk
- For themes present in both snapshots, show three badges per row: Confidence Δ, Segment shifts, ARR-at-risk Δ. - Confidence Δ formatting: signed value with 1 decimal (e.g., +0.7); thresholds: ≥+0.5 green, ≤−0.5 red, otherwise gray. - ARR-at-risk Δ uses org currency; increases (higher risk) red, decreases green, zero gray; tooltip shows previous, current, and formula. - Segment shifts badge shows +[added]/−[removed] and top 3 segment names moved; clicking opens a modal with full segment changes. - Badges include accessible labels announcing metric name and delta; color is not the sole indicator (icon + text present).
Quick Filters: Segment, Product Area, Severity
- Filters are multi-select with counts; applying filters updates both panes and theme map within 400ms p75. - Results count and badge totals recalculate consistently across panes; zero-state shows "No themes match" with Clear All action. - Selected filters render as chips with removal X; Clear All removes all filters in one action. - Filter state persists in URL (filters=[...]); reloading or sharing restores the same filtered view. - Filtering never shows a theme in one pane without the corresponding row (placeholder shown if missing due to snapshot differences).
Expandable Theme Details: Quotes and Metrics
- Clicking a theme row expands details in both panes; expansion state syncs across panes for the same theme. - Each pane shows up to 5 representative quotes with source (channel, date) and link to the original item; links open in a new tab. - Quotes moved between clusters display a "Moved" label with from→to theme names; at least one representative moved quote is shown when applicable. - Metrics section displays current vs previous values for volume, confidence, ARR-at-risk side-by-side; values match badges and header totals. - Collapse/expand is keyboard accessible (Enter/Space); Esc collapses; expanded theme IDs persist in URL (expanded=[...]).
Hover/Focus Tooltips for Metric Definitions
- Hovering or focusing a metric label shows a tooltip within 150ms containing definition, formula, and last updated timestamp. - Tooltips are focusable, dismiss on Esc or blur, and maintain aria-describedby relationships to their triggering controls. - Tooltip placement avoids viewport clipping; smart flip enabled; contrast meets WCAG AA (≥4.5:1 for text). - Tooltip content is identical across panes for the same metric and snapshot context.
Deep Links to Clusters, Tickets, and Jira/Linear Items
- Clicking a theme title opens the cluster detail in a new tab with snapshot context (clusterId, snapshotTs) in query params; page returns HTTP 200. - Ticket links open the source system (e.g., Zendesk) with correct ticket ID; Jira/Linear links include project/issue key; open in new tab. - If Jira/Linear integration is not configured, clicking shows a connect modal; upon successful connect, the original action retries automatically. - All external links append UTM params (utm_source=echolens&utm_medium=deltadiff&utm_campaign=side_by_side) and preserve referrer policy. - Broken/dead links show an inline, non-blocking error with a Retry action and Copy Link option.
Snapshot Capture & Versioning
"As a CX lead, I want to capture and select precise snapshots to compare so that I can anchor discussions to reproducible states."
Description

Adds snapshotting of the theme map and ranked queue at specific times or release markers, including cluster memberships, confidence scores, segment distributions, ARR mapping, and representative quotes. Stores immutable, timestamped versions with metadata (creator, comparison notes) and ensures deterministic re-computation for reproducibility. Exposes APIs to list, retrieve, and diff snapshots, and enforces retention policies and access control. Enables users to select two snapshots for Delta Diff and guarantees consistent IDs to map clusters across versions.

Acceptance Criteria
Manual Snapshot at Release Marker
Given a workspace with an up-to-date theme map and ranked queue and a user with Snapshot:Create permission When the user triggers "Create Snapshot" with a release marker and optional comparison notes (UI or API) Then the system enqueues the snapshot within 2 seconds and returns 201 with snapshot_id, checksum, and created_at (UTC) And the snapshot captures cluster memberships, confidence scores, segment distributions, ARR mappings, and representative quotes exactly as of trigger time And for datasets ≤ 500k feedback items, the snapshot job completes within 5 minutes at p95 and updates the UI timeline within 10 seconds of completion
Immutable Storage, Metadata, and Audit
Given a created snapshot When any update attempt is made to its content or metadata Then the API returns 409 Conflict and no changes are persisted And the snapshot metadata includes creator_id, created_at (UTC ISO 8601), release_marker, labels[], and comparison_notes (≤2000 chars) And an audit log entry is recorded for create, view, diff, delete with actor_id, action, timestamp, and snapshot_id And retrieving the snapshot always yields the same checksum as at creation
Deterministic Re-computation and Stable IDs
Given the same input corpus, model version, and configuration seed When a snapshot is recomputed in a clean environment Then the content hash, cluster_ids, confidence scores, segment distributions, ARR mappings, and representative quote sets are identical And cluster_ids remain stable across snapshots; unchanged clusters retain their id with ≥99% membership overlap And split/merge/relabel events are emitted with mapping metadata (origin_ids, new_ids, operation) to support cross-version mapping
Snapshot APIs: List, Retrieve, Diff with RBAC
Given an authenticated user in a workspace When they call GET /snapshots with filters (creator_id, release_marker, date range) and pagination (cursor, limit≤100) Then results are scoped to their workspace, sorted by created_at desc by default, and returned in <500 ms p95 for up to 1000 total snapshots When they call GET /snapshots/{id} Then 200 returns the full snapshot payload (gzip supported) with ETag==checksum, or 404 if not found, or 403 if cross-workspace When they call POST /snapshots/diff with two valid snapshot_ids Then 200 returns confidence deltas, segment share deltas, ARR-at-risk delta, moved quotes, and cluster mapping; 400 for invalid input; 403 for insufficient rights And RBAC: Owner/Admin/Analyst can create and diff; Viewer can list and retrieve; no role can mutate snapshot content
Retention Policy and Legal Hold Enforcement
Given workspace retention_days=N and a snapshot with no legal hold When the snapshot age exceeds N days Then it is soft-deleted within 24 hours and hard-deleted within 7 days, with a tombstone retained for audit (snapshot_id, deleted_at, deleter=system) And snapshots under legal_hold=true are excluded from deletion until the hold is cleared And deleted snapshots are excluded from list endpoints, selection UIs, and Delta Diff options
Delta Diff Selection and Accurate Delta Display
Given two snapshots in the same workspace When a user opens Delta Diff and selects snapshot A and snapshot B Then the side-by-side view loads within 2 seconds after data fetch p95 and displays per-cluster confidence deltas (±), segment distribution deltas, ARR-at-risk deltas, and representative quotes moved between clusters And totals reconcile: moved quote count equals the membership difference across A and B; numeric deltas match backend diff within ±0.01 And clusters are mapped by stable ids; unmatched clusters are clearly labeled as New or Removed
Confidence Delta Computation
"As a data-informed PM, I want accurate confidence deltas with noise controls so that I trust the diffs and prioritize appropriately."
Description

Calculates per-cluster and overall confidence deltas between two snapshots with statistical guards to reduce noise. Implements thresholding, significance heuristics, and directionality labels (increase/decrease/stable) and surfaces uncertainty via confidence intervals. Supports drill-down to the underlying evidence (tickets, reviews, chats) and ties into the model outputs used by the live theme map to avoid duplication. Emits structured metrics consumable by the UI and by exports/shares.

Acceptance Criteria
Per-Cluster Confidence Delta With Directionality and Confidence Intervals
Given two snapshots S_pre and S_post for the same workspace and a cluster_id present in both, and config {alpha=0.05, delta_threshold=0.02, min_sample_n=30} When the confidence delta computation runs Then for that cluster the output contains cluster_id, snapshot_pre_id, snapshot_post_id, sample_sizes.pre, sample_sizes.post, delta_value, confidence_interval_95.lower, confidence_interval_95.upper, directionality, significance_flag And delta_value equals (confidence_post - confidence_pre) rounded to 3 decimals And directionality is 'increase' if confidence_interval_95.lower > 0, 'decrease' if confidence_interval_95.upper < 0, else 'stable' And significance_flag is true only if p_value < alpha and sample_sizes.pre >= min_sample_n and sample_sizes.post >= min_sample_n And confidence_interval_95.lower < confidence_interval_95.upper and both lie within [-1, 1]
Overall Confidence Delta Aggregation Across Snapshots
Given snapshots S_pre and S_post and config overall_aggregation='volume_weighted' When the overall confidence delta is computed Then overall_confidence_pre equals sum_i(pre_n_i * conf_pre_i) / sum_i(pre_n_i) to 3 decimals And overall_confidence_post equals sum_i(post_n_i * conf_post_i) / sum_i(post_n_i) to 3 decimals And overall.delta_value equals (overall_confidence_post - overall_confidence_pre) rounded to 3 decimals And overall includes confidence_interval_95 and directionality computed by the same rules as per-cluster And significance_flag for overall obeys alpha and min_sample_n applied to total sample sizes
Noise Reduction via Thresholding and Significance Heuristics
Given a cluster where abs(delta_value) < delta_threshold OR either sample size < min_sample_n OR confidence_interval_95 spans 0 OR p_value >= alpha When labeling and filtering deltas Then directionality is 'stable' And significance_flag is false And is_filtered is true in the payload for that cluster And is_filtered is false only when the delta is both above threshold and statistically significant
Segment-Level and ARR-at-Risk Shift Computation
Given segments (e.g., plan, region) and account-level ARR_at_risk aligned to evidence assignments for S_pre and S_post, and config {segment_delta_threshold=0.02, min_segment_sample_n=20} When per-cluster deltas are computed Then affected_segments includes only segments where sample_sizes.pre >= min_segment_sample_n OR sample_sizes.post >= min_segment_sample_n and abs(delta_value) >= segment_delta_threshold And each segment entry includes segment_id, sample_sizes.pre, sample_sizes.post, delta_value (3 decimals), significance_flag, arr_at_risk_shift_cents (post - pre as integer cents) And arr_at_risk_shift_cents equals the sum of ARR_at_risk for unique accounts mapped to the cluster in S_post minus the corresponding sum in S_pre And segment directionality and significance_flag follow the same rules as per-cluster
Drill-Down From Delta to Underlying Evidence and Moved Items
Given a cluster delta record and snapshots S_pre and S_post When requesting drill-down for that record Then the API returns evidence[] where each item has {evidence_id, source_type in ['ticket','review','chat','email'], snapshot_id in [S_pre, S_post], cluster_id, account_id (optional), quote_text} And the API returns moved_between_clusters[] where each item has {evidence_id, prev_cluster_id, new_cluster_id, quote_text} And representative_quotes_moved contains at least 3 items when >=3 moved items exist, otherwise all moved items And every evidence_id returned exists in the evidence store and is unique within its list And the count of unique evidence items per snapshot reconciles with sample_sizes for the cluster within 1% after deduplication
Consistency With Live Theme Map Model Outputs (No Duplication)
Given the live theme map model outputs for S_pre and S_post When validating delta computation lineage Then every cluster record includes model_version and embedding_version that match the theme map versions for both snapshots And for every evidence assignment used in delta computation, the cluster_id assignment equals the theme map's assignment for the same snapshot And no duplicate cluster_id entries exist in the clusters[] payload And a lineage_id composed of snapshot_pre_id, snapshot_post_id, and model_version is present and identical across all cluster records
Structured Metrics Payload for UI and Export Consumers
Given a completed delta computation run When emitting metrics Then the payload includes schema_version='1.0.0' and type='confidence_delta' And top-level fields include snapshot_pre_id, snapshot_post_id, generated_at (ISO-8601), overall, clusters[] And each clusters[] item includes cluster_id, cluster_name, sample_sizes.pre (int), sample_sizes.post (int), delta_value (number), confidence_interval_95.lower (number), confidence_interval_95.upper (number), directionality in ['increase','decrease','stable'], significance_flag (boolean), is_filtered (boolean), arr_at_risk_shift_cents (int), affected_segments[] (array), representative_quotes_moved[] (array) And the JSON validates against schema id 'delta_diff.confidence_delta.v1' with zero validation errors And the exports/shares endpoint returns a payload that is deep-equal to the UI API payload for the same run
Segment Impact Diff
"As a CX analyst, I want to understand which customer segments got more or less affected so that I can coordinate targeted outreach."
Description

Computes and visualizes changes in affected customer segments between two snapshots, including counts, proportions, and trend direction. Supports segment hierarchies (account tier, industry, region, platform) and enables filtering and grouping in the UI. Provides tooltips and legends to clarify segment definitions and calculates net-new, expanded, and reduced segment coverage per cluster. Outputs machine-readable segment delta data for downstream analytics.

Acceptance Criteria
Side-by-Side Segment Delta Visualization
Given two snapshots S1 and S2 are selected for one or more clusters in Delta Diff When the Segment Impact Diff view loads Then each visible segment row shows S1_count, S2_count, S1_prop, S2_prop, delta_count, delta_prop, and trend_direction for that segment Then S1_prop = S1_count / total_items_S1_at_level and S2_prop = S2_count / total_items_S2_at_level Then delta_count = S2_count - S1_count and delta_prop = S2_prop - S1_prop (percentage points) Then trend_direction displays Up if delta_prop > +0.1pp, Down if delta_prop < -0.1pp, Flat otherwise Then numeric values match backend computation exactly and are formatted with thousands separators; proportions show one decimal place Then columns are labeled with S1 and S2 snapshot timestamps and totals; default sorting is by absolute delta_prop descending and can be toggled to other sort keys
Segment Hierarchy Drilldown and Rollup
Given the segment hierarchy levels account_tier > industry > region > platform are available When the user switches the grouping level or expands a parent to view children Then the segment list updates within the selected hierarchy level without a full page reload Then parent counts and proportions equal the sum of their immediate children within 0.1% rounding tolerance Then the breadcrumb reflects the current path and a Reset to Top-Level control returns to the highest level Then previously applied filters remain in effect after level changes
Filtering and Grouping Respect Segment Deltas
Given filters for cluster(s), date range, segment types, and platform are available When the user applies or clears a filter or changes grouping Then all counts, proportions, deltas, and trend_direction recompute using only items matching active filters Then the visible segment list contains only segments with non-zero counts after filtering unless the Show Zeroes toggle is on Then active filters are displayed as chips; removing a chip updates the view accordingly Then the filter state persists when navigating away and back within the Delta Diff feature in the same session
Net-New, Expanded, Reduced Coverage Classification
Given two snapshots S1 and S2 for a selected cluster When segment coverage state is computed Then each segment is labeled Net-New if S1_count = 0 and S2_count > 0, Reduced if S2_count < S1_count, Expanded if S2_count > S1_count, and Unchanged if S2_count = S1_count Then the legend colors map uniquely to these four states and remain consistent across views Then a summary pill shows counts of segments in each state and totals equal the number of visible segments Then clicking a state filter displays only segments with that state
Tooltips and Legend Clarify Segment Definitions
Given the user hovers, focuses, or taps a segment name, metric, or legend entry When a tooltip or legend is displayed Then the tooltip shows the segment path (e.g., account_tier > industry > region > platform), definition, S1_count, S2_count, S1_prop, S2_prop, delta_prop calculation formula, and snapshot timestamps Then the legend defines colors/symbols for Net-New, Expanded, Reduced, and Unchanged and explains the trend arrow logic Then all tooltips are keyboard accessible (focusable targets, ESC to dismiss) and meet WCAG AA color contrast Then a Data Freshness note shows the last ingest time and data sources included
Machine-Readable Segment Delta Export
Given an authenticated user requests segment delta data via Export button or API endpoint /v1/segment-deltas with snapshot_id_s1, snapshot_id_s2, cluster_id(s), hierarchy_level, and filters When the export is triggered Then the response returns within 2 seconds for up to 10,000 segment rows and includes fields: cluster_id, segment_path, level, s1_count, s2_count, s1_prop, s2_prop, delta_count, delta_prop_pp, trend_direction, coverage_state, snapshot_id_s1, snapshot_id_s2, filters_applied Then the exported values match the UI values for the same parameters exactly Then invalid parameters return 400 with a machine-readable error; unauthorized requests return 401; no matching data returns 204 Then a CSV download option uses the same schema with headers and UTF-8 encoding
ARR-at-Risk Delta Calculation
"As a revenue-focused PM, I want ARR-at-risk changes calculated reliably so that I can escalate high-impact issues with evidence."
Description

Calculates ARR-at-risk changes per cluster and in aggregate between snapshots by joining cluster membership to account revenue and weighting by recency and severity. Handles partial attribution, deduplication across multiple signals from the same account, and caps to prevent overstatement. Displays absolute and percentage change with clear currency formatting and links to the list of implicated accounts. Respects permissioning for revenue data and includes audit logs for access.

Acceptance Criteria
Per-Cluster ARR Delta Between Snapshots
Given two snapshots S1 (prior) and S2 (current) and a cluster C with account memberships and ARR at each snapshot And workspace currency and locale are configured When computing delta for C Then absolute_delta = weighted_attributed_ARR(C,S2) - weighted_attributed_ARR(C,S1) And percentage_change = (absolute_delta / weighted_attributed_ARR(C,S1)) rounded to two decimals, displayed as "—" if weighted_attributed_ARR(C,S1) = 0 And currency values render with workspace currency symbol/code, thousands separators, and two decimals And if any account moved into or out of C between S1 and S2, its attributed ARR is subtracted from the source cluster and added to the destination cluster accordingly And the cluster row displays a clickable link "Implicated accounts (N)" that opens the pre-filtered accounts list for C and S2
Aggregate ARR Delta Across Clusters
Given all visible clusters with computed deltas between S1 and S2 When displaying the aggregate ARR-at-risk delta Then aggregate_absolute_delta equals the sum of cluster absolute deltas after partial attribution and deduplication rules, within ±0.01 of workspace currency due to rounding And aggregate_percentage_change equals (sum_current - sum_prior) / sum_prior rounded to two decimals, displayed as "—" if sum_prior = 0 And clusters excluded by filters or permissions are excluded from the aggregate calculation and UI totals
Partial Attribution Across Clusters
Given an account A appears in multiple clusters within a snapshot with weights w_i When attributing A’s ARR to clusters Then for each cluster i, contribution_i = ARR_A * w_i And per snapshot, sum_i(w_i) = 1.0; if provided weights are missing or sum_i(w_i) ≠ 1.0, normalize deterministically to sum to 1.0 And the sum of contributions across clusters equals ARR_A within ±0.01 And snapshot-specific weights are used for their respective snapshots when computing deltas
Deduplication of Multi-Signal Accounts
Given an account produces multiple signals across channels within the same cluster and snapshot When aggregating signals to compute attributed ARR Then the account contributes at most once per cluster per snapshot regardless of the number of signals And duplicate signals from the same source are dropped by unique signal ID And across clusters, contribution splits follow partial attribution weights And cross-snapshot comparisons avoid double counting by using snapshot-scoped membership and weights
Recency and Severity Weighting Application
Given signals have recency_score and severity_score and the workspace has weighting coefficients wr and ws When computing an account’s pre-normalized cluster weight Then pre_weight = sum_over_signals(recency_score*wr + severity_score*ws) clipped to [0, +∞) And per-account weights across its clusters are normalized to [0,1] and sum to 1.0 in that snapshot And default coefficients (wr=1, ws=1) are used if none are configured And unit tests using a fixed fixture produce deterministic weights matching expected values to 4 decimal places
Capping and Overstatement Prevention
Given account-level ARR caps and workspace capping rules are enabled When computing attributed ARR across clusters for an account within a snapshot Then total_attributed_ARR_across_clusters ≤ account_ARR And per-cluster attributed ARR ≤ account_ARR And deltas may be negative; no floor is applied to deltas, but caps still apply to positive sides And test fixtures confirm that adding additional signals beyond cap does not increase attributed ARR
Revenue Permissioning and Audit Logging
Given a user without revenue permission views the Delta Diff screen When ARR-at-risk deltas are displayed Then absolute and percentage ARR values and implicated accounts links are hidden or masked per workspace policy, and a permission notice is shown Given a user with revenue permission opens the implicated accounts list for a cluster When the list loads Then an audit log entry is written with user_id, timestamp, snapshot_ids, cluster_id, account_ids accessed, action="view_revenue", success=true And the entry is queryable via admin audit logs within 5 minutes and retained per retention policy
Quote Movement Traceability
"As a support lead, I want to see which quotes moved between clusters so that I can craft representative narratives with proper attribution."
Description

Tracks and displays representative quotes and their movement between clusters across snapshots, preserving provenance to original sources (ticket, review, chat) and hashing to detect duplicates. Highlights quotes that changed cluster, were added, or were removed, and shows rationale scores or embeddings similarity where available. Enables one-click copy and insertion into Jira/Linear updates and exports, while maintaining PII redaction rules and source access controls.

Acceptance Criteria
Snapshot-to-Snapshot Quote Movement Classification
Given a baseline snapshot S1 and a comparison snapshot S2 for the same workspace When the Delta Diff view is opened Then each representative quote is classified as one of: Moved (cluster changed), Added (present in S2 only), Removed (present in S1 only), or Unchanged (cluster unchanged) Given quotes classified as Moved When the user expands a quote Then the UI shows prior cluster name/id and new cluster name/id with snapshot IDs Given movement counts per cluster When totals are displayed Then sum(Moved + Added + Removed + Unchanged) equals the number of unique quotes across S1 ∪ S2 Given up to 10,000 representative quotes across S1 and S2 When the Delta Diff renders Then the movement list populates p95 within 2 seconds and p99 within 4 seconds
Quote Provenance and Source Linking
Given a representative quote When displayed in Delta Diff Then it shows source type (ticket/review/chat/email), source ID, timestamp, and a deep link to the original system Given the viewer lacks permission to access the source When the deep link is clicked Then access is denied with a permission notice and no PII is revealed Given an authorized viewer When the deep link is clicked Then the original item opens in a new tab with correct item identifier and workspace mapping Given a quote derived from multiple messages in one thread When displayed Then all contributing message IDs are listed in chronological order
Stable Hashing and Duplicate Detection
Given two texts identical after normalization (lowercasing, trimming, collapsing whitespace, stripping URLs/session IDs) When hashed Then they produce the same quote_hash value Given two distinct texts that differ beyond normalization rules When hashed Then they produce different quote_hash values Given duplicate quotes across S1 and S2 with identical quote_hash When movement is computed Then duplicates are merged and counted once and do not inflate movement counts Given a reference test set of 10,000 quotes with 500 known duplicate pairs When hashing and deduplication runs Then duplicate recall is ≥ 0.98 and false positive rate is ≤ 0.005
Rationale Scores and Embedding Similarity Display
Given a quote that changed clusters When expanded Then the UI displays the rationale score and/or cosine similarity to the previous and new cluster centroids Given rationale or similarity is unavailable When expanded Then the UI displays "N/A" with a tooltip explaining the missing signal Given similarity values are shown When rendered Then values are normalized to [0,1] with two decimal places and labeled as Prev cluster and New cluster Given workspace-level low-confidence thresholds are configured When a value falls below the threshold Then the quote row shows a warning icon and can be filtered/sorted by confidence
One-Click Copy and Jira/Linear Insert
Given one or more quotes are selected When "Copy for update" is clicked Then the clipboard contains a formatted block with quote text (redacted), movement type, prev→new cluster, snapshot IDs, and source link(s) Given a Jira or Linear project is connected When "Insert to Issue" is clicked on a chosen issue Then the formatted block is appended as a comment or description update within 3 seconds p95 and a success confirmation is shown Given the integration is disconnected or an API error occurs When "Insert to Issue" is clicked Then an actionable error is displayed with retry and connect options and no partial content is posted Given the user selects Export When exporting selected quotes Then CSV and JSON include fields: quote_hash, text_redacted, movement_type, prev_cluster_id/name, new_cluster_id/name, snapshot_ids, rationale_score, similarity_prev, similarity_new, source_type/id/url
PII Redaction and Access Control Enforcement
Given PII redaction is enabled with patterns for emails, phone numbers, credit cards, addresses, and JWTs When quotes render in UI or exports Then matching substrings are replaced with category tokens such as [EMAIL] and [PHONE] Given a user without PII override permission When copying, exporting, or inserting quotes Then only redacted text is included and no raw PII is present in payloads or integration posts Given an admin with "Reveal PII" permission When they click "Reveal once" on a quote Then the full text is shown for that session only and an audit log records user, timestamp, quote_hash, and action Given source access control denies the current user When a source link is opened Then the system does not proxy or cache source content and logs a denied-access event
Shareable Before/After Report & Sync
"As a product manager, I want a shareable before/after report and Jira/Linear sync so that I can broadcast changes without manual formatting."
Description

Generates a concise, branded before/after report from a selected Delta Diff, summarizing top deltas, affected segments, ARR-at-risk shifts, and key quotes. Supports shareable links with access controls, PDF export, and one-click sync as a comment or attachment in Jira/Linear linked issues. Includes configurable templates for PM and CX audiences and embeds deep links back to the Delta Diff view. Captures share metrics (views, copies) for follow-up.

Acceptance Criteria
Generate Branded Before/After Report from Delta Diff
Given I have selected a Delta Diff and an audience template (PM or CX) When I click Generate Report Then the report is generated within 5 seconds And it includes the top 5 increases and top 5 decreases in confidence with scores and % change And it lists affected segments with counts and % of total feedback And it shows ARR-at-risk change (absolute and %) for the selected time window And it includes up to 3 representative quotes per moved cluster with source type and date And it displays workspace logo, report title, author, and ISO-8601 timestamp
Shareable Link with Access Controls
Given a generated report When I create a share link with visibility set to Private, Org-Only, Public, or Password-Protected and optionally set expiration Then the link enforces the selected visibility (unauthorized requests return 403; expired or revoked return 410) And views are logged with timestamp and viewer identity (workspace user id or anonymous token) And I can revoke the link and it immediately becomes inaccessible And a toggle for Allow Copy controls whether the copy button appears to viewers
PDF Export Fidelity and Performance
Given a generated report When I export as PDF Then the PDF matches on-screen content including highlights, charts, and branding And text is selectable and links are clickable And the PDF is generated within 10 seconds for a 10-page report and is <= 10 MB And the document includes page numbers and report metadata (title, author, created date)
One-Click Sync to Jira/Linear
Given a generated report with a linked issue in Jira or Linear When I click Sync to Issue and choose Comment or Attachment Then the system posts a comment containing the summary, ARR-at-risk delta, and a link to the shareable report, or uploads the PDF as an attachment accordingly And the sync is idempotent per report+issue (re-sending within 24h does not duplicate content) And I receive a success confirmation including the external issue id and timestamp And on failure a descriptive error is shown and I can retry
Configurable Templates for PM and CX Audiences
Given templates exist with placeholders {top_deltas}, {segments}, {arr_delta}, {quotes} When I select PM or CX template and preview Then all placeholders render with data with no unresolved tokens And switching templates re-renders the preview with the new structure within 1 second And my last-used template selection is remembered for my user in this workspace
Embedded Deep Links Back to Delta Diff
Given a generated report When I click a deep link on a delta, segment, or quote Then the Delta Diff view opens to the same diff_id with filters applied and the target element scrolled into view within 2 seconds And the URL contains diff_id and target identifiers to allow sharing of the exact context
Share Metrics Capture and Display
Given a shareable report link When viewers access or use the copy button Then total views, unique viewers, and copy events are recorded with timestamps And I can view these metrics per report in the UI within 15 minutes of the event And exporting metrics as CSV produces accurate counts for the selected date range

Change Gate

Role-based approvals and policies for sensitive edits (e.g., high-impact themes or top-10 queue changes). Enforce reviewer sign-off, require reason codes, and notify in Slack—reducing accidental drift while keeping a transparent trail of who approved what and when.

Requirements

RBAC for Sensitive Edits
"As a workspace admin, I want to restrict who can perform sensitive edits so that only authorized users can change high-impact items."
Description

Define and enforce role-based permissions for all sensitive actions, including editing high-impact themes, re-ranking the top-10 queue, merging/splitting themes, deleting themes, and modifying Change Gate policies. Integrate with existing user management and SSO/SCIM groups to map roles (e.g., Admin, Approver, Editor, Viewer). Apply server-side authorization checks to all mutation endpoints with context-aware constraints (e.g., item impact level, queue position). Ensure API tokens inherit least-privilege permissions. Provide admin UI for role assignment and permission audits to prevent unauthorized changes and reduce accidental drift.

Acceptance Criteria
Server-side RBAC on Sensitive Mutation Endpoints
Given a user with role Viewer or unauthenticated When they call any sensitive mutation endpoint (edit high-impact theme; re-rank top-10; merge/split theme; delete theme; modify Change Gate policies) Then the API responds 403 with error_code=RBAC_DENIED and no data changes occur Given a user with role Editor When they attempt any sensitive action requiring Approver/Admin per policy Then the API responds 403 with error_code=RBAC_DENIED and an audit entry is recorded Given a user with role Approver or Admin When they perform a permitted sensitive action Then the API responds 2xx, applies the change atomically, and records an audit entry with actor, role, resource_id(s), and timestamp Given any client When RBAC evaluation fails due to missing policy or ambiguous role Then the API responds 403 with error_code=RBAC_MISCONFIGURED and no change is applied
Context-Aware Constraints for High-Impact Themes and Top-10 Queue
Given role Editor When editing a theme whose impact_level=High Then the API returns 403 RBAC_DENIED and logs the attempt Given role Editor When re-ranking to move any item into positions 1–10 Then the API returns 403 RBAC_DENIED and the queue order remains unchanged Given role Approver When re-ranking within positions 1–10 or editing High-impact themes Then the API responds 2xx and applies the change Given role Editor When re-ranking items outside positions 11+ or editing Low/Medium-impact themes Then the API responds 2xx and applies the change Given any non-Admin When attempting to modify Change Gate policies Then the API returns 403 RBAC_DENIED; Given role Admin, the same request succeeds with 2xx
SSO/SCIM Role Mapping and Sync to EchoLens Roles
Given an IdP group-to-role mapping is configured (e.g., Okta group "EchoLens-Approvers" -> Approver) When a user is added to or removed from the mapped group Then the user's effective role in EchoLens updates within 10 minutes and is enforced in RBAC decisions Given a user with no matching SSO/SCIM group When they sign in Then their default role is Viewer Given conflicting group mappings for a user When role resolution occurs Then the configured precedence is applied and the chosen role plus source is recorded in audit metadata Given a user is deactivated in the IdP or SCIM deprovisioned When they attempt API/UI access Then access is blocked within 10 minutes and active sessions are invalidated Given SCIM provisioning fails When a role change event is received Then the system retries with backoff, alerts Admins, and affected users remain least-privilege (Viewer) until success
Least-Privilege API Tokens with Role Inheritance
Given a user creates an API token When no explicit scope is selected Then the token defaults to least privilege (read-only, Viewer-equivalent) Given a user attempts to create a token with permissions exceeding their own role When the request is submitted Then the API rejects it with 400 INVALID_SCOPE and no token is created Given a valid API token When calling sensitive endpoints beyond its scope/role Then the API returns 403 RBAC_DENIED and includes token_id in the audit record Given a token is revoked by its owner or an Admin When subsequent requests use that token Then the API responds 401 UNAUTHORIZED within 60 seconds of revocation Given a user with role Approver creates a token scoped to Editor When the token is used Then the effective permissions are the intersection (Editor) and cannot exceed the creator's role
Admin UI for Roles Management and Permission Audits
Given role Admin When accessing Settings > Roles & Access Then the page loads within 2 seconds and lists users with current role and source (SSO/Local) with search and sort controls Given role Admin When changing a user's role or adding a local override Then the UI requires a reason code, the change is applied within 1 minute, and an audit entry is created with actor, target_user, old_role, new_role, and reason Given non-Admin roles (Approver, Editor, Viewer) When attempting to access Roles & Access or Permission Audit Then access is denied with 403 and no user or audit data is revealed Given role Admin When filtering the Permission Audit by action type, resource, actor, or date range and exporting Then the filtered results match the export exactly and include timestamps in UTC
Comprehensive Audit Trail for Sensitive Actions
Given any allowed or denied attempt to perform a sensitive action When the action is processed Then an immutable audit record is written containing event_id, actor_id, actor_role, auth_method (UI/API/token), action, resource identifiers, pre/post value snapshot or diff summary, timestamp (UTC), outcome (allowed/denied), and reason_code if provided Given an Admin or Approver queries the audit log by resource_id or actor_id and date range When up to 10,000 matching events exist Then the results return within 3 seconds and are paginated deterministically Given stored audit records When accessed via public APIs Then they are read-only; deletion/modification is not permitted; export is available to Admins only; retention is at least 1 year Given an audit record for a denied action When viewed Then it includes the RBAC decision reason (e.g., insufficient_role, context_constraint) and the attempted endpoint
Policy Rule Engine
"As a product owner, I want to define rules that trigger approvals for sensitive changes so that governance is consistent and automatic."
Description

Provide a configurable policy engine that determines when approvals are required based on conditions such as theme impact score thresholds, confidence scores, top-N queue position, data source type, environment (prod/staging), and action type (edit, delete, merge, re-rank, external sync). Policies can be scoped by workspace or project and include exception lists. Include a UI to create, test, and validate rules with previews. At runtime, evaluate policies per proposed change to gate actions, generate a policy trigger summary, and route the change into an approval workflow when criteria are met.

Acceptance Criteria
Approval Required for High-Impact Theme Edit
Given a workspace policy requiring approval when impact_score >= 8.0 OR confidence_score < 0.6 for action types [edit] in environment=prod with required_roles=[Owner,Admin], min_approvers=1, reason_code_required=true And the actor has role=Editor and is not on any exception list And the target theme has impact_score=8.2 and confidence_score=0.58 And environment=prod When the actor attempts to save edits to the theme Then the policy engine evaluates within 200 ms and blocks the direct save And produces a trigger summary including matched_rule_ids, matched_attributes, required_roles, min_approvers, and reason_code_required=true And opens an approval request with status="Pending", linked to the change and a unique decision_id And posts a Slack message to the configured channel within 5 seconds including decision_id and trigger summary And writes an immutable audit record capturing actor_id, UTC timestamp, before/after diffs, decision_id, and rule versions
Top-10 Queue Re-Rank Requires Approval with Preview
Given a project policy requiring approval when target_position <= 10 for action_type=[re-rank] And the item is currently at position=23 And the actor proposes new_position=7 and provides reason_code="Hotfix" When the actor initiates the re-rank in the UI Then a pre-submit policy preview displays the triggering rule, required approvers, and whether approval is required And submitting routes the change to an approval workflow instead of applying it immediately And the approval request records reason_code and proposed_position=7 And the live queue remains unchanged until the request is approved And the preview and the created approval request show consistent trigger summaries
Environment-Aware Approval Policies (Prod vs Staging)
Given an organization policy scoped to environment=prod for action_type=[external sync] And no equivalent policy exists for environment=staging And the actor initiates an external sync from EchoLens to Jira When environment=staging Then the sync executes immediately without approval and the audit record notes no-approval reason env=staging When environment=prod Then the action is gated, an approval request is created, and the sync is deferred until approval or auto-cancelled after a 72-hour timeout
Action-Type Rule: Delete/Merge Requires Elevated Approval
Given a project policy requiring min_approvers=2 with required_roles=[Owner,Admin] for action_types=[delete,merge] on data_sources in ["Tickets","Emails"] And the actor proposes a merge of two themes sourced from "Tickets" When the merge is submitted Then the engine blocks auto-approval and requires two distinct approvers satisfying required_roles And the proposer cannot self-approve unless explicitly allowed by policy And Slack notifications are sent to the project channel and to approver DMs And the approval request details indicate which data_sources triggered the rule
Exception List Bypass for Trusted Actors/Projects
Given a workspace policy that would otherwise require approval for the edit And user_id=123 and project_id="Growth" are on the policy's exception lists When user_id=123 edits a high-impact theme in project "Growth" Then the change saves immediately without approval And the audit record includes exception_applied=true, exception_types=["user","project"], and policy_id And no Slack approval notification is sent
Rule Builder: Create, Validate, and Test with Sample Payloads
Given the Rule Builder UI When a user creates a policy with conditions impact_score >= 7.5 AND top_n <= 5 AND action in [edit,re-rank], scope=workspace "Acme", environment=prod, min_approvers=1, required_roles=[Admin], reason_code_required=true Then the form validates all fields and prevents save on invalid/missing entries with inline error messages And the user can run "Test with Sample Payload" by pasting JSON of a proposed change And the system returns a deterministic preview including would_gate=true/false, matched_rule_ids, matched_attributes, required_roles, min_approvers, reason_code_required, and a human-readable explanation And the UI warns on policy conflict/overlap with an existing active policy and offers merge or priority setting And upon save, the policy is versioned with version, created_by, created_at (UTC), and change_note
Policy Trigger Summary Generated and Routed to Workflow
Given any change evaluated as gated by the policy engine When evaluation completes Then a trigger summary object is generated containing decision_id (UUID), policy_ids, rule_versions, matched_attributes with evaluated values, scope identifiers (workspace_id, project_id), approver requirements, reason_code_required, environment, and action_type And the summary is attached to the approval request, displayed in the UI, included in the Slack message, and stored in the audit log And the summary is retrievable via API GET /approvals/{decision_id} and matches the audit record And the policy evaluation P95 latency is <= 250 ms under 50 RPS sustained load
Multi-level Approval Workflow
"As an approver, I want a clear workflow to review and approve changes so that sensitive updates are controlled and timely."
Description

Support single- or multi-step approval flows with approver assignment by role, user, or group. Implement a state machine covering Submitted, Pending Approval, Changes Requested, Approved, Rejected, and Expired. Enforce SLAs, due dates, and escalations to backups or managers when overdue. Allow inline feedback and resubmission after changes are requested. Require all configured steps to pass before applying changes; automatically apply or discard changes based on final decision.

Acceptance Criteria
Sequential Multi-Step Approval Applies Changes
Given a change request configured with two sequential approval steps (Theme Owner, then CX Lead) and resolvable approvers And the requester submits the change for approval When the Theme Owner approves with a required reason code within the configured SLA Then the workflow advances to Pending Approval for the CX Lead And the Theme Owner can no longer change or revoke their decision When the CX Lead approves with a required reason code within the configured SLA Then the workflow state changes to Approved And the system applies the proposed changes atomically And the live theme map and ranked queue reflect the changes within the configured propagation window And Slack notifies the requester and watchers with approver names and reason codes And the audit trail records approvers, timestamps, reason codes, and SLA compliance
Rejection Discards Pending Changes
Given a change request is Pending Approval at any step When an authorized approver for the current step rejects and provides a required reason code Then the workflow state changes to Rejected And all proposed changes are discarded and not applied And the live theme map and ranked queue remain unchanged And Slack notifies the requester and watchers of the rejection and reason And the audit trail records the rejection, approver identity, timestamp, and reason code
Changes Requested with Inline Feedback and Resubmission
Given a change request is Pending Approval at any step When the approver selects Changes Requested and submits inline comments and a required reason code Then the workflow state changes to Changes Requested And the requester can view inline feedback and edit the change without creating a new request When the requester resubmits Then the workflow returns to Pending Approval for the same step And prior comments and diffs remain visible in the timeline And the SLA timer resets according to the configured policy And Slack notifies the approver that a resubmission is ready
SLA Overdue Escalation to Backup and Manager
Given a change request is Pending Approval with a configured due date/SLA and designated backup and manager When the due date passes without action Then the system escalates to the backup approver via Slack and email within the configured escalation window And the audit trail records the escalation event and recipients When the backup approves or rejects Then that decision is accepted for the step and the primary approver is prevented from acting When the backup does not act within the backup escalation window Then the system escalates to the manager and records the event And any first valid decision (approve/reject/changes requested) from backup or manager completes the step
Expiration Closes Request Without Applying Changes
Given a change request is Pending Approval and has an expiration policy configured When the request exceeds the maximum allowed age without a decision Then the workflow state changes to Expired And no changes are applied And Slack notifies the requester with guidance to clone and resubmit And the audit trail records expiration time and policy that triggered it And the requester can create a new submission from the expired request with one click
Approver Resolution by Role/User/Group and Reason Code Enforcement
Given an approval policy defines approvers by role, user, or group When a change request is submitted Then the system resolves approvers for each step at submission time And submission is blocked if any step has no resolvable approver, with an actionable error And resolved approvers are displayed per step to the requester and recorded in the audit trail When any approver attempts to approve, reject, or request changes without selecting a reason code from the allowed list Then the action is blocked with a validation error And the chosen reason code is stored and visible on the decision event
State Machine Integrity and Concurrency Controls
Given the workflow supports states: Submitted, Pending Approval, Changes Requested, Approved, Rejected, Expired Then only valid transitions are permitted: Submitted→Pending Approval; Pending Approval→Approved|Rejected|Changes Requested|Expired; Changes Requested→Pending Approval; Approved|Rejected|Expired are terminal And attempts to act on a terminal state are blocked with a clear error When two approvers submit conflicting decisions concurrently for the same step Then only the first valid decision is persisted, and later decisions are rejected as stale And all side effects (notifications, audit entries, change application) occur exactly once (idempotent)
Reason Codes & Justification
"As a requester, I want to supply standardized reasons for changes so that reviewers understand intent and we can audit decisions later."
Description

Require submitters of gated changes to select a standardized reason code and optionally provide a free-form justification. Allow admins to manage the taxonomy of reason codes and define validation rules (e.g., mandatory justification, minimum length). Persist reasons with the change record for search, analytics, and audit. Offer templates and quick-pick suggestions to speed up submission while maintaining clarity of intent for reviewers.

Acceptance Criteria
Require reason code for gated change submission
Given a user initiates a gated change submission When the submission modal opens Then the Reason Code control is visible, populated with all Active reason codes And the Submit button remains disabled until a reason code is selected And selecting a reason code enables Submit and clears any inline error And attempting to submit without a reason code displays an inline error "Reason code is required" And the selected reason code persists if the user navigates away and returns before submitting
Enforce justification rule and minimum length
Given an admin has configured Justification Required = true with Minimum Length = N When a user attempts to submit with a justification length < N (after trimming whitespace) Then the submission is blocked and an inline error shows "Justification must be at least N characters" And when the justification length >= N Then submission validation passes for this rule And a live character counter reflects the trimmed length And when Justification Required = false Then submission succeeds without justification, regardless of Minimum Length
Admin manages reason code taxonomy
Given an admin is on Reason Codes settings When they create a new reason code with a unique name Then it is saved as Active and appears in the submission control upon refresh And names must be unique case-insensitively; attempts to duplicate are blocked with a clear error And admins can edit name and description of a reason code And admins can deactivate a code; deactivated codes are hidden from new submissions but remain on historical records And deletion is prevented if a code is referenced by any change record; an explanatory error is shown
Persist and audit reason metadata with change
Given a gated change is submitted and passes validation When the change record is created Then the system persists: reason_code_id, reason_code_label_snapshot, justification_text, submitter_id, created_at, policy_config_version And the persisted values are read-only on the record; any later edits create a new versioned audit entry capturing prior and new values, editor_id, and edited_at And the change detail view and API both return the persisted reason and justification
Search and analytics by reason code and justification
Given change records exist with various reason codes and justifications When a user filters the Change Log by one or more reason codes Then only matching records are returned and counts reflect the applied filters And a filter for "Has Justification" returns only records with non-empty justification And exports and analytics aggregations include reason code and justification presence fields
Templates and quick-pick suggestions for justification
Given templates and quick-pick suggestions are configured When the user focuses the Justification field Then a suggestions panel appears with admin-defined templates and system quick-picks And choosing a template inserts its text into the field at the cursor, without disabling further edits And users can remove inserted text before submitting And admins can create, edit, and deactivate templates; deactivated templates no longer appear in suggestions
Reviewer and Slack visibility of reasons
Given a reviewer opens a pending approval Then the selected reason code label and justification text are visible above the approval controls And when the change is submitted, a Slack notification to the configured channel includes the reason code label and the first 200 characters of the justification (or 'No justification provided') And clicking the Slack message opens the change detail view with the reason section focused
Slack Notifications & Reminders
"As a reviewer, I want to be notified in Slack and act quickly so that approvals don’t block releases."
Description

Integrate with Slack to send notifications on submission, assignment, approval, rejection, changes requested, and SLA breaches. Support channel- and DM-based targeting, message threading per change, and actionable buttons or deep links that respect RBAC. Allow configurable reminder cadences, quiet hours, and per-policy notification settings. Log notification delivery status and include links to the change preview and policy trigger summary.

Acceptance Criteria
Submission Notification with Content and Links
Given a sensitive edit is submitted under a Change Gate policy and Slack is connected When the submission is saved Then a Slack notification is sent to the configured target(s) within 5 seconds including: change title, actor, reason code, policy name, and timestamps And the message includes a deep link to the change preview and a link to the policy trigger summary And delivery_status is logged as Sent with Slack message_id and channel/user id
RBAC Enforcement for Actionable Slack Buttons
Given a Slack notification contains Approve, Reject, and Request Changes actions for a gated change When a user with approver role clicks an action Then the system performs the action, updates the change state, posts a confirmation reply in the Slack thread within 3 seconds, and records an audit log with Slack user id Given a user without required permissions clicks an action When the click is processed Then no state change occurs, an ephemeral "Insufficient permission" message is shown to that user, and a denied-attempt audit log entry is created Given a user opens a deep link without access When the link is resolved Then show an access denied view and do not reveal change details
Policy-Based Routing to Channel and/or DM
Given a policy routes notifications to a Slack channel and assignee DM When a change triggers that policy Then send one message to the channel and one DM to the assignee with identical content and links, avoiding duplicates per recipient And if per-policy notifications are disabled, do not send any Slack messages and log delivery_status as Suppressed Given a user has opted out of DMs for this policy When the notification is emitted Then send only the channel message and record the opt-out in delivery metadata Given a channel or user mapping is invalid When attempting delivery Then log delivery_status as Failed with error code and surface an admin alert with remediation guidance
Threading per Change Lifecycle
Given a change has no existing Slack thread in the target destination When the first notification is sent Then create a parent message, persist thread_ts as change_thread_id, and tag the message with the change id Given subsequent events (assignment, approval, rejection, changes requested) for the same change When notifications are sent Then post as replies within the same thread_ts and include event-specific labels Given the original parent message is missing or deleted When posting a follow-up Then create a new parent, persist the new thread_ts, link it to the change, and continue threading; only one active thread per change per channel is maintained
Reminder Cadence and Quiet Hours
Given reminders are configured as every 12h up to 5 times and quiet hours are 22:00–07:00 workspace time When an approval is pending Then schedule reminders at the specified cadence outside quiet hours; label the fifth as Final; each send decrements remaining_count Given the change state becomes approved, rejected, or changes requested When the state change is recorded Then cancel all pending reminders within 30 seconds and record cancellation reason Given a user snoozes reminders for 2 hours from Slack When a reminder would target that user Then suppress delivery to that recipient until snooze expires while delivering to others
SLA Breach Escalation
Given a 24h approval SLA is configured for the policy When no approval occurs within 24h of submission Then send an SLA breach notification to the escalation channel and DMs to the policy owner and current assignee containing hours overdue, change link, and policy summary; add an "SLA Breached" label Given an SLA breach notification has already been sent for this change When additional time passes without resolution Then do not send duplicate breach notices; subsequent reminders use "Still Breached" wording and reference the original thread Given SLA is breached and later resolved When the change is approved or closed Then post a resolution update in the thread and stop breach-related reminders
Delivery Logging, Retries, and Observability
Given any Slack notification attempt When Slack returns 200 OK Then persist delivery_status=Sent with message_id, channel/user id, thread_ts (if applicable), response timestamp, and payload checksum Given Slack returns a transient error (5xx or rate_limited) When attempting delivery Then retry up to 2 times with exponential backoff (1s, 4s); record each attempt; after final failure set delivery_status=Failed with last error Given Slack returns a non-retryable 4xx (invalid_auth, channel_not_found, user_not_found) When attempting delivery Then do not retry; set delivery_status=Failed and create an admin alert with a direct remediation link Given a message is successfully sent When viewing the notification log entry Then it includes the exact change preview URL and policy trigger summary URL used in the Slack message
Change Diff & Preview
"As a requester, I want to preview the exact changes and their impact so that I submit accurate edits and reviewers can assess risk."
Description

Render a structured, human-readable diff of proposed changes before submission and during review, including field-level changes, previous vs. new values, expected effects on queue rank and theme map, and downstream impacts on Jira/Linear sync. Display which policies triggered the gate and why. Enable inline comments and mentions for clarification, and ensure previews are available via secure links in Slack notifications.

Acceptance Criteria
Pre-Submission Diff Preview for Sensitive Edit
Given a user with edit permissions proposes a change that triggers Change Gate When they open the preview before submitting Then the diff displays field-level changes with previous and new values side-by-side, highlighting additions in green, removals in red, and modifications in yellow And the preview includes changed field name, old value, new value, and change type for each change And the preview renders within 2 seconds for up to 25 field changes at the 95th percentile And a Submit for Review action is present and remains disabled until all required fields (e.g., reason code if enforced) are provided
Reviewer Diff Preview with Policy Triggers and Reason Codes
Given a reviewer opens a pending change in Change Gate When viewing the diff preview Then a Policy Triggers panel lists each triggered policy with name, rule summary, matched condition, and timestamp And the submitted reason code and rationale (if required by policy) are displayed and immutable And required approvers are listed with current approval status (Pending/Approved/Rejected) per approver And the Approve action is disabled while any required approver (other than the actor) is missing or while policy checks are failing, with an explanatory tooltip
Queue Rank and Theme Map Impact Projection
Given the proposed change impacts prioritization or clustering When the preview is generated Then the preview shows predicted queue rank deltas for all impacted items, including previous rank, projected rank, and delta, up to the top 50 items And items with absolute delta ≥ 3 are flagged with an Impact badge And the theme map section shows nodes/edges added/removed/merged with per-item confidence scores and a mini-map overlay And the model version and timestamp used for the projection are displayed
Jira/Linear Downstream Sync Impact
Given Jira or Linear sync is enabled for the workspace When viewing the preview Then the preview lists downstream changes per integration: counts of issues to create/update/close and field-level diffs for each affected issue And each item shows project, issue key (or placeholder if not yet created), and target fields to be modified And secrets or tokens are redacted in all displays And if no downstream impacts are detected, the section states "No downstream impacts" And a View in Jira/Linear link opens the mapped entity in a new tab for existing issues
Inline Comments and Mentions on Diff
Given a user with comment permission is viewing the diff When they select a changed field Then they can add an inline comment and @mention users or teams with autocomplete And mentioned users receive notifications in-app and via Slack/email based on their preferences within 60 seconds And comments support edit/delete by author, resolve/unresolve, and threaded replies And viewers without comment permission cannot add or edit comments And each diff section displays a badge with the count of unresolved comments
Secure Slack Notification with Preview Link
Given a Slack notification is sent for a proposed change or review request When a recipient clicks the preview link in Slack Then they are routed via an expiring, signed URL that requires authentication and authorization And the link expires after 7 days by default and can be configured between 1 and 30 days at the workspace level And Slack unfurl shows only title, environment, and change ID without any sensitive field values And all accesses are logged with user, timestamp, IP, and outcome And unauthorized or expired access returns HTTP 403 with no resource existence leakage
Stale Data and Conflict Handling in Preview
Given the underlying record has changed since the preview was generated When the user returns to the preview or attempts to submit/approve Then the system detects staleness via version/ETag and displays a Recalculate Preview banner And the diff recalculates within 2 seconds at the 95th percentile or provides a manual Recalculate option on failure And if a conflicting change touched the same field, the preview shows a conflict state with mine vs. theirs values and disables submit/approve until resolved And all recalculations and conflict resolutions are added to the audit trail
Immutable Audit Trail & Export
"As a compliance lead, I want an immutable audit trail of approvals and changes so that we can demonstrate control and investigate incidents."
Description

Capture an append-only record of every sensitive action and approval decision, including timestamps, actors, diffs, reason codes, policy triggers, and notification events. Protect integrity with tamper-evident hashing and restricted write paths. Provide workspace- and theme-level audit views with filters by user, action, status, and time range. Offer export to CSV/JSON and read-only API endpoints for compliance reporting and incident investigations.

Acceptance Criteria
Append-Only Audit Log for Sensitive Actions and Approvals
Given a user performs a sensitive action (e.g., high-impact theme edit, top-10 queue re-order, approval decision, or policy change) When the action is committed Then an audit event is appended within 500ms containing: event_id (UUIDv4), ISO8601 UTC timestamp to milliseconds, actor user_id and role, resource type and id, action type, before/after field-level diff, reason_code (if provided), policy_trigger ids matched, action_outcome (success|failure), and correlation_id Given an audit event is stored When attempting to update or delete the event via any API or UI Then the system must reject the operation with 405 or 403 and log a separate tamper-attempt event without altering the original Given an action requires a reason code per policy When the user omits the reason code Then the action is blocked with 400 and no change is applied, and an audit event with outcome=failure and error_code=REASON_CODE_MISSING is appended
Tamper-Evident Hash Chain and Restricted Write Path
Given the audit store has an existing last_hash When a new audit event is appended Then event.hash = SHA-256(canonical_event_json + last_hash) and store.last_hash is updated to event.hash Given any audit event in the chain is modified at rest When the integrity verification job runs Then the chain verification fails, the first invalid event_id is reported, and an integrity_breach event is appended in a separate protected store Given a caller without audit.write service role attempts to write to the audit store via DB or API When the request is processed Then the write is denied (403/blocked), no audit data is changed, and a security_denied event is recorded Given a verifier calls GET /audit/integrity?from=<ts>&to=<ts> When the range covers N events Then the API returns 200 with range_first_hash, range_last_hash, verified=true, and count=N within 2s for up to 50k events
Workspace and Theme-Level Audit Views with Filters
Given a workspace admin opens the Audit view scoped to a workspace When they filter by actor=userX, action in [approve,edit], status in [success,pending], and time range (last 30 days) Then the table shows only matching events, with total_count and pagination accurate to ±0, and results ordered by timestamp desc Given a theme owner opens the Audit view for Theme T When no filters are applied Then only events where resource_id=T and resource_type=theme are displayed Given the audit view renders 10k matching events When the user pages and sorts by timestamp desc Then first page loads within 2s and each next page within 1s with stable ordering across pages Given a user clicks an event row When the details panel opens Then it shows full diff (only changed fields), actor, policy triggers, reason code, notifications emitted, and hash values
Audit Export to CSV and JSON
Given a user with export permission selects filters in the Audit view When they click Export CSV Then a file named audit_<scope>_<YYYYMMDDThhmmssZ>.csv is downloaded containing header columns: event_id,timestamp,actor_id,actor_role,resource_type,resource_id,action,action_outcome,reason_code,policy_triggers,diff,correlation_id,hash,last_hash; data is UTF-8, comma-delimited, with values quoted as needed Given the same filters When the user clicks Export JSON Then a NDJSON file is downloaded with one canonical_event_json per line plus the hash and last_hash fields Given a filtered result set exceeds 1,000,000 events When export is requested Then a streamed export is initiated, user is notified, and a link is delivered via email/Slack within 10 minutes; partial exports are not allowed Given no events match the filters When export is requested Then a zero-row CSV with headers or an empty NDJSON file is produced and returns 200 Given an auditor calls GET /audit/export?format=json&filters=... with a valid token When the request succeeds Then the response includes Content-Disposition, page/chunk metadata, and a range_root_hash covering the exported set
Read-Only Audit API for Compliance and Investigations
Given a client calls GET /audit/events with token scope=audit.read When filters (actor, action, status, time range, resource) are supplied Then the API returns 200 with paginated results (limit<=1000, cursor-based), correct total_count if requested, and ETag headers Given a client attempts POST/PUT/PATCH/DELETE on /audit/* endpoints When the request is received Then the API returns 405 Method Not Allowed (or 403 if applicable) and no state change occurs Given a client without audit.read scope calls GET /audit/events When the request is processed Then the API returns 401 (no auth) or 403 (insufficient scope) Given a client exceeds 600 requests per minute per workspace When continuing to call the API Then responses return 429 with Retry-After and no degradation to other tenants
Capture of Notification Events and Outcomes
Given a sensitive action triggers a Slack notification per policy When the notification is sent Then the audit log appends a notification event linked by correlation_id including channel_id, message_ts, delivery_status=sent, and template_id Given Slack API returns an error When the action is processed Then the audit log appends a notification event with delivery_status=failed and an error_code, while the original action event outcome remains success or failure as executed Given workspace notifications are disabled by policy When a sensitive action occurs Then the audit log appends a notification event with delivery_status=skipped and reason=notifications_disabled Given multiple destinations (e.g., Slack and email) are configured When an action fires notifications Then each destination yields a separate notification event linked to the same correlation_id
Approval Trail Completeness for Policy-Gated Changes
Given a policy requires N approvers and a reason code for changes to top-10 queue When a requester submits a change Then an audit event is appended with outcome=pending and required_approvals=N and reason_code present Given approvers provide sequential approvals When the final approval is submitted Then an approval_decision event is appended for each approver with timestamp and actor, and the original pending event is closed by a final action event with outcome=success; no state transition occurs without all required approvals Given the requester is also an approver and self-approval is disallowed by policy When they attempt to approve their own request Then the system blocks the action (403) and appends an audit event with outcome=failure and error_code=SELF_APPROVAL_BLOCKED Given a policy configuration is changed (e.g., approver count) When the change is saved Then a policy_change event is appended capturing before/after values, actor, reason_code (if required), and policy_trigger references

Impact Simulator

Pre-commit “what-if” modeling for merges, splits, and score tweaks. See predicted queue reordering, confidence shifts, and business impact before you save—helping teams reach consensus faster and avoid costly thrash.

Requirements

Live Queue Reorder Preview
"As a product manager, I want to see how my edits will reorder the queue before I commit so that I can anticipate tradeoffs and avoid unintended priority changes."
Description

Pre-commit, real-time visualization of the ranked queue changes caused by proposed merges, splits, and score weight adjustments. As users manipulate clusters or weights, the simulator recomputes the ranking and overlays delta indicators (rank up/down, confidence shift, and predicted impact) for each theme. Includes filters for product area, segment, and timeframe, and respects existing constraints (pinned items, SLAs). Runs on the same scoring pipeline as production in read-only mode to guarantee fidelity while isolating changes from the live queue. Target round-trip under 2 seconds for 10k themes via incremental recalculation and caching.

Acceptance Criteria
Real-time Preview for Weight Adjustments
Given a workspace containing 10,000 themes and an active baseline ranked queue When the user changes one or more score weights in the Impact Simulator Then the preview recomputes the ranked queue using the production scoring pipeline in read-only mode And then for each visible theme the UI overlays: rank delta (arrow + signed integer), confidence delta (signed decimal), and predicted impact, all derived from baseline vs preview And then the 95th-percentile round-trip latency from weight change to fully rendered preview is <= 2.0 seconds And then the live production queue and scores remain unchanged until the user explicitly commits changes
Preview Fidelity and Isolation to Production Pipeline
Given identical inputs and no proposed changes When the simulator recomputes Then the resulting ranks, scores, and confidences exactly match production outputs (tolerance <= 1e-6) When merges, splits, or weight changes are applied in preview and not saved Then no write operations occur to production data stores or downstream integrations and no webhooks/alerts fire And then the simulator uses the same scoring pipeline version and configuration as production for the session
Merge and Split Reorder Preview
Given two clusters A and B selected in the simulator When the user previews a merge of A and B Then themes in A or B are rescored and the queue reorders accordingly; themes outside A and B retain their scores And then delta overlays for affected themes reflect correct rank and confidence shifts relative to baseline When the user previews a split of cluster C into subclusters Then rescoring and queue reordering reflect the proposed split And then toggling the proposed change off restores the baseline ordering and overlays within 2.0 seconds
Filter-Scoped Preview: Product Area, Segment, Timeframe
Given product area, segment, and timeframe filters are applied When the user manipulates clusters or weights Then the preview recalculation, queue, and delta overlays are scoped exclusively to the filtered dataset And then filter selections persist across consecutive preview operations and do not reset on recomputation And then items outside the filters are not displayed and do not contribute to any aggregate predicted impact shown in the preview
Constraint Compliance: Pinned Items and SLA Rules
Given a set of pinned items and SLA-governed items in the baseline When the preview recomputes after merges, splits, or weight changes Then pinned items remain in their pinned positions and SLA-governed items remain compliant with current SLA ordering rules And then no preview state allows a constraint violation; ordering is adjusted to satisfy constraints before display And then delta indicators for constrained items reflect changes relative to their constrained positions, not unconstrained scores
Incremental Recalculation and Caching
Given a known impacted set from a single weight change or a merge/split operation When the preview executes Then only themes whose scores depend on the changed inputs are recomputed; all other themes’ scores remain unchanged to within 1e-6 And then reverting the change within the same session uses cached results and restores the prior state without recomputing unaffected items And then the round-trip latency for revert operations is <= 2.0 seconds for a 10k-theme workspace
Sandbox Merge & Split Editor
"As a CX lead, I want to test merges and splits safely so that I can improve theme quality without risking the live queue."
Description

A dedicated sandbox where users can drag-and-drop to merge similar themes or split heterogeneous clusters without affecting production. The editor surfaces similarity metrics, coverage stats, and representative examples to inform decisions, and validates operations against guardrails (e.g., minimum support, duplicate prevention). All operations emit a reversible action list consumed by the simulator for preview and by the commit step for execution. Keyboard shortcuts and bulk selection support fast triage.

Acceptance Criteria
Open Sandbox Editor and Production Isolation
Given I open the Sandbox Merge & Split Editor from the Impact Simulator When I create, modify, or remove sandbox operations without committing Then no changes are applied to the production theme map or ranked queue And the editor displays a visible "Sandbox" indicator And closing or canceling the editor without committing discards all sandbox operations
Drag-and-Drop Merge of Themes with Metrics
Given two or more themes are selected in the sandbox editor When I drag one theme onto another and drop, or select Merge from the toolbar Then a single merged theme is created in the sandbox and the originals are marked as merged-only within the sandbox context And similarity metric, coverage stats, and confidence score for the merged theme are displayed adjacent to the result And a "Merge" action with operands and computed deltas is appended to the reversible action list
Split Cluster via Representative Examples
Given a cluster is selected and items within it are eligible for separation When I select one or more items or representative examples and choose Split Then a new theme is created in the sandbox containing the selected items, and the original cluster retains the remainder And similarity metrics, coverage stats, and representative examples are displayed for both resulting themes And a "Split" action with operands and computed deltas is appended to the reversible action list
Guardrails: Minimum Support and Duplicate Prevention
Given I attempt a merge or split operation When the resulting theme would fall below the configured minimum support threshold Then the operation is blocked, the control is disabled, and an inline error states the threshold value And the Commit action remains disabled while any guardrail errors exist When I attempt an identical merge or split already present in the action list, or attempt to merge a theme with itself Then the duplicate operation is prevented and the action list remains unchanged
Reversible Action List and Undo/Redo
Given one or more sandbox operations exist When I open the action list panel Then each operation displays type (merge/split), operands (IDs/names), timestamp, and predicted impact deltas When I press Cmd/Ctrl+Z Then the most recent operation is undone and both the UI and preview reflect the prior state When I press Cmd/Ctrl+Shift+Z Then the last undone operation is reapplied and recorded as active
Simulator Preview of Proposed Changes
Given one or more sandbox actions exist When I click Preview changes Then the simulator displays predicted ranked queue reordering, confidence shifts, and business impact deltas relative to production And the preview clearly indicates "Not Committed" and offers Commit and Cancel options And modifying sandbox actions updates the preview without a page reload
Bulk Selection and Keyboard Shortcuts
Given I am viewing the theme list in the sandbox editor When I select multiple themes via checkboxes, Shift-click, or drag-select Then the selection count and aggregated coverage stats are displayed And pressing M merges the selected themes (available only when two or more are selected) And pressing S initiates Split for the currently selected cluster (available only when exactly one cluster is selected) And pressing Esc clears the selection and Cmd/Ctrl+A selects all themes in view And shortcut-triggered actions are equivalent to their UI counterparts and are recorded in the action list
Score Weighting Controls
"As a product leader, I want to tweak signal weights in a scenario so that the ranking reflects our current strategy without permanently changing workspace settings."
Description

Interactive controls (sliders and presets) to adjust the weights of ranking signals such as volume, recency, revenue impact, churn risk, and model confidence. Changes immediately feed the simulator to show queue, confidence, and impact deltas. Presets (e.g., “Stability,” “Growth,” “Churn Defense”) encode common strategies, with role-based permissions to restrict who can change global defaults versus per-scenario tweaks. Reset-to-default and audit of changes included.

Acceptance Criteria
Real-Time Slider Adjustment Updates Simulator
Given a loaded scenario in Impact Simulator with default weights When the user releases a dragged weight slider for any signal (e.g., Volume) Then the ranked queue, confidence scores, and business impact deltas recalculate and render within 1 second And the top 50 items display position delta indicators relative to the current baseline And the modified slider displays a dirty-state indicator until saved or reset And no page reload is required
Applying Strategy Preset Reweights Signals and Shows Deltas
Given presets "Stability", "Growth", and "Churn Defense" are available When the user selects the "Churn Defense" preset Then all signal weights update to the preset-defined values And the active preset name is shown as selected And the simulator updates queue, confidence, and impact deltas within 1 second And a Revert action restores the immediately previous weights for the current session
Per-Scenario Tweaks Do Not Modify Global Defaults
Given a non-admin user with permission to edit scenario settings When the user changes one or more weights and saves the scenario Then the scenario stores those weights as an override for that scenario only And the organization’s global default weights remain unchanged And opening another scenario reflects the unchanged global defaults And the current scenario is labeled "Using overrides" with a link to view the diff from global defaults
Role-Based Permission Gate for Global Default Changes
Given a user without the "Admin: Weight Defaults" permission When they attempt to save current weights as the global default Then the Save as Global Default control is disabled or shows a permission error And no changes are committed to global defaults And a blocked-attempt audit event is recorded with user ID and timestamp Given a user with the "Admin: Weight Defaults" permission When they save as global default Then a confirmation modal is shown and, upon confirm, global defaults are updated and timestamped
Reset to Default Restores Baseline Weights and View
Given weights are modified from the organization’s global default When the user clicks Reset to Default Then all signal weights revert to the current global default values And any per-scenario overrides are cleared for the active scenario session And the simulator refreshes to baseline queue, confidence, and impact within 1 second And all position delta indicators reset to neutral
Audit Log Records Weight Changes with Diff and Scope
Given a user applies a weight change (manual slider or preset) and saves When the change is committed (scenario or global) Then an audit entry is created with: user ID, UTC timestamp, scope (scenario/global), before/after values for each signal, and preset name if used And Reset to Default actions also create an audit entry And audit entries are visible in the Audit view, filterable by date (last 24h, 7d) and scope, and exportable to CSV
What-If Summary Reflects Weight Changes Consistently
Given the simulator displays a summary of moves and impact after weight adjustments When the user increases any signal weight and releases the control Then the summary shows counts of items moved up, down, and unchanged that sum to the total items in view And the total predicted impact delta equals the sum of item-level deltas shown And a queue checksum/hash changes when ordering changes and remains the same when no ordering change occurs
Confidence & Business Impact Projections
"As a stakeholder, I want to see projected confidence and business impact of my scenario so that I can justify prioritization decisions with data."
Description

The simulator estimates post-change confidence scores and predicted business impact using the existing scoring model plus uplift heuristics from historical fix outcomes. It renders uncertainty bands and callouts for items sensitive to the change, and aggregates scenario-level KPIs (e.g., expected tickets reduced, ARR at risk, NPS delta). Projections are clearly labeled as estimates and linked to methodology docs to build trust.

Acceptance Criteria
Previewed Queue Reordering from Merges, Splits, and Score Tweaks
Given a baseline queue with no saved changes, when a user proposes a merge of two themes, then affected items show updated ranks with up/down deltas and a "Preview" banner is visible until saved. Given a proposed split of a theme into two child themes, when the preview is generated, then items are redistributed per historical signal distribution heuristic and top 50 ranks update accordingly. Given a confidence score tweak (+/- percentage or absolute), when the preview is generated, then each affected item's projected confidence and rank deltas are displayed next to the baseline values. Given identical input changes, when the preview is regenerated, then the resulting ordering and projections are deterministic and match a scenario hash shown to the user. Given no proposed changes, when preview is requested, then the UI indicates "No changes to preview" and the ordering remains identical to baseline.
Projected Confidence and Business Impact Calculations
Given scoring model version and uplift heuristics from historical fix outcomes, when a scenario is previewed, then projected confidence scores equal the model output within ±0.5% of a reference implementation for a validation set. Given items lacking sufficient historical outcome data, when projections are computed, then uplift contribution defaults to zero and the UI labels the uplift as "insufficient data" for those items. Given business impact metrics (tickets reduced, ARR at risk, NPS delta), when projections are computed, then each metric is produced per item and summarized with units and time horizon clearly shown. Given rounding and display rules, when values are rendered, then confidence is shown to one decimal place and monetary values use currency formatting with the workspace currency.
Uncertainty Bands and Sensitivity Callouts
Given item-level projections, when preview is generated, then 80% and 95% uncertainty bands are displayed for confidence and impact metrics with hover tooltips showing numeric ranges. Given an item whose relative 95% interval width exceeds 30% or predicted rank volatility exceeds 10 positions, when preview is shown, then the item is flagged with a "Sensitive" callout. Given sensitivity callouts, when a user expands details, then the top contributing factors (e.g., low sample size, high uplift variance, model drift) are listed with their contribution percentages. Given an item without sufficient variance data, when preview is shown, then uncertainty bands are replaced by a "Not enough data" indicator.
Scenario-Level KPI Aggregation and Display
Given item-level projected deltas, when a scenario is previewed, then scenario-level KPIs (expected tickets reduced/week, ARR at risk in currency, NPS delta in points) are displayed above the queue with baseline vs scenario comparison. Given numeric aggregation, when KPIs are computed, then the sum of item-level deltas reconciles to scenario totals within 1% after rounding. Given data recency settings, when KPIs are displayed, then the lookback window (e.g., last 90 days) is shown and can be changed, and KPI values update within 500 ms after a change. Given a scenario with zero net change, when KPIs are displayed, then all KPI deltas show zero with a neutral indicator.
Trust, Labeling, and Methodology Links
Given any projection, when values are displayed, then a clear "Estimates" label is visible in the header and next to KPI totals. Given the methodology link, when a user clicks "How this is calculated", then the methodology documentation opens in a new tab at the relevant anchor within 300 ms. Given a model version and last-trained timestamp, when a scenario is previewed, then both are displayed and included in exports and screenshots. Given per-metric info icons, when a user hovers or clicks, then definitions, formulas, and assumptions are visible without leaving the page.
Performance, Determinism, and Non-Destructive Workflow
Given a workspace with up to 10,000 items and a 90-day lookback, when a user previews a single change (merge/split/tweak), then the p95 time to first paint of projections is ≤ 2 seconds and p99 ≤ 5 seconds. Given multiple sequential edits in the same session, when preview is recalculated, then the scenario hash updates and results remain deterministic for the same inputs. Given the user cancels or navigates away without saving, when they return, then baseline data remains unchanged and no projections were persisted. Given a backend or model error, when preview fails, then a non-blocking error state is shown with retry and baseline remains visible; no partial projections are saved.
Scenario Save, Share, and Compare
"As a team member, I want to save and share scenarios and compare them side by side so that we can align quickly on the best approach."
Description

Ability to save a simulation as a named scenario with notes, then share a read-only link for review. Users can duplicate scenarios, compare two side-by-side with diff views of rank, confidence, and impact changes, and mark one as the candidate for commit. Scenarios capture the data snapshot timestamp and all parameters (merges/splits, weights) to ensure reproducibility.

Acceptance Criteria
Save Named Scenario with Snapshot and Parameters
Given I have an active simulation with merges, splits, and weight adjustments When I choose "Save as scenario", enter a non-empty unique name, optionally add notes, and click Save Then the system persists the scenario with an immutable snapshot timestamp, the complete parameter set (merges/splits, weights), and the computed ranks, confidence, and impact values And I see a success confirmation and the scenario appears in my scenario list within 2 seconds And saving with an empty name is blocked with inline validation And attempting to reuse a name that already exists in the same project is blocked with a clear error message
Share Read-only Scenario Link
Given a saved scenario When I generate a share link Then the link opens a read-only view showing the scenario name, notes, snapshot timestamp, parameters (merges/splits, weights), and computed results And viewers cannot modify parameters, save changes, duplicate, mark candidate, or delete; all mutation controls are disabled And any edit API calls from the read-only view are rejected with HTTP 403 And the share view renders the scenario exactly as saved based on the stored snapshot, regardless of subsequent data changes
Duplicate Scenario for Variant Exploration
Given a saved scenario When I choose Duplicate Then a new scenario is created with the same snapshot timestamp, parameters, and results, with the name prefixed "Copy of <original>" And the duplicate is editable without affecting the original And the duplicate appears in the scenario list immediately after creation
Side-by-Side Comparison with Diffs
Given two saved scenarios in the same project When I open Compare and select both scenarios Then I see a side-by-side view listing themes with each scenario's rank, confidence, and impact, and per-theme deltas (Δrank, Δconfidence, Δimpact) And items with any change are visually highlighted, and reordering is indicated by rank up/down markers And the default sort is by absolute rank change descending
Mark Scenario as Candidate for Commit
Given multiple saved scenarios in a project When I mark one scenario as Candidate Then it is flagged as the sole Candidate in that project And marking a different scenario as Candidate removes the flag from the previously marked scenario And the Candidate flag is visible in the scenario list, detail, and compare views
Reproduce Results from Snapshot
Given a saved scenario with a snapshot timestamp and parameters When I load the scenario at any later time Then the ranks, confidence, and impact values match the originally saved results exactly for all items captured in the snapshot And the system indicates the snapshot timestamp used to render the results
Scenario Metadata Visibility
Given a saved scenario When I open the scenario details or read-only share view Then the snapshot timestamp, the list of merges/splits applied, and the weight values used are displayed And the displayed metadata matches the parameters used to compute the scenario results
Data Freshness & Drift Guardrails
"As a user, I want to know if the data has changed since I started simulating so that I don’t commit outdated or invalid changes."
Description

The simulator displays the data snapshot time and detects underlying data drift (new feedback, model changes) during an open session. If drift is detected, it prompts users to refresh and re-run or proceed with caution, and blocks commits when conflicts exceed defined thresholds. A “refresh and rebase” action reapplies the scenario’s operations against the latest data, highlighting any invalidated steps.

Acceptance Criteria
Snapshot Timestamp Display
Given I open the Impact Simulator and the backend provides snapshot_id and snapshot_timestamp (UTC) When the simulator initializes the session Then the UI displays the snapshot timestamp in the user’s local timezone and ISO 8601 UTC on hover And the timestamp remains constant for the session until a refresh occurs And the displayed time matches the backend snapshot_timestamp within 1 second And an aria-label "Data snapshot taken at <local time> (<UTC>)" is present for screen readers
Drift Detection During Open Session
Given an Impact Simulator session is open with snapshot_hash H And server-side data changes (new feedback) or a model version update produce snapshot_hash H' When the client receives a drift signal via websocket or detects via a 30s poll Then a "Data drift detected" banner appears within 5 seconds of detection And the banner indicates drift type (new data, model change, both) and counts (e.g., +N items, model vX→vY) And severity is shown as minor if affected_clusters_ratio < 5%, major if 5%–20%, critical if > 20% (value supplied by backend)
Prompt Refresh or Proceed with Caution
Given drift is detected and conflicts_ratio is below the blocking threshold When the user clicks Commit or Preview Impact Then a modal appears with actions: "Refresh & Rebase" (primary), "Proceed with caution", and "Cancel" And the modal summarizes predicted effects (queue reorder count, confidence deltas, potentially invalidated steps) And selecting "Proceed with caution" requires a justification note (min 10 characters) and logs audit event "commit_on_drift_proceeded" And selecting "Cancel" closes the modal with no state changes
Block Commits on Conflict Threshold Exceeded
Given admin setting commit_block_threshold is 15% and invalid_ops_block is true And the current scenario has conflicts_ratio = 18% or invalid_operations_count > 0 When the user attempts to Commit Then the Commit action is disabled with a tooltip explaining the block (e.g., "Conflicts 18% exceed 15% threshold" or "2 invalid operations") And an inline error lists conflicting steps and metrics And no changes are persisted server-side
Refresh and Rebase Operation
Given a session with pending operations (merges, splits, score tweaks) ordered O1..On And drift has been detected When the user selects "Refresh & Rebase" Then the latest snapshot S' is fetched and O1..On are replayed against S' in original order And replay completes within 5s for n ≤ 50 operations and within 15s for n ≤ 200 And operations that cannot be applied are marked invalid and skipped without aborting replay And the simulator state (theme map, queue preview, confidence deltas) reflects S' plus valid replays And an audit log "refresh_rebase" records prior snapshot_id, new snapshot_id, operations_reapplied, operations_invalidated
Highlight Invalidated Steps After Rebase
Given one or more operations were invalidated during Refresh & Rebase When the rebase completes Then invalidated steps are highlighted in the scenario timeline with a red status and a reason (e.g., "cluster no longer exists", "score target changed") And a summary chip shows "<k> of <n> steps invalidated" And clicking an invalidated step opens details with original context, current state, and recommended next actions And the user can export an invalidation report (CSV) and copy a shareable link And the Commit button remains disabled until all invalidated steps are resolved or removed
Safe Commit with Jira/Linear Diff Sync
"As a PM, I want to commit an approved scenario and sync the changes to Jira/Linear so that downstream teams immediately see the updated priorities with full traceability."
Description

On approval, the system applies the scenario to the live queue atomically, writes an audit log (who, what, when, why), and syncs diffs to Jira/Linear: update labels/links on existing issues, add comments summarizing rank and confidence changes, and create tasks for newly promoted themes when configured. Includes pre-commit validation, rollback on failure, and success notifications with links to affected items.

Acceptance Criteria
Atomic Commit and Audit Log on Approval
Given an approved Impact Simulator scenario with a computed diff against the live queue And there are N affected themes and M linked Jira/Linear issues When the user clicks Commit and confirms Then the live queue is updated in a single atomic operation producing a new queueVersionId And all N affected themes reflect the predicted rank and confidence values from the scenario And any read of the queue during the operation returns either the previous version or the new version, never a partial mix And exactly one audit log entry is created containing: actorId, actorEmail, scenarioId/hash, queueVersionId, timestamp, changeSummary (counts and deltas), and a non-empty reason ("why") And the audit log entry is retrievable via API and UI within 5 seconds
Pre-commit Validation Blocks Unsafe Commits
Given a scenario that fails validation (e.g., missing project mapping, permission check fails, or stale base version) When the user attempts to commit Then the commit is rejected before any queue or external system changes occur And the user sees a structured error describing each failing rule with codes and remediation tips And no audit log success entry is created (only a validation-failed entry with details) And zero Jira/Linear API requests are made And the Commit button remains disabled until issues are resolved or the scenario is refreshed
Jira/Linear Sync: Update Labels and Links on Existing Issues
Given affected themes map to existing Jira/Linear issues When the commit succeeds Then each mapped issue is updated exactly once to reflect the new theme link and required labels/tags And only issues referenced in the diff are updated; unrelated issues remain unchanged And updates preserve existing unrelated labels and fields And each external API call returns 2xx And the audit log stores per-issue ids and status "updated"
Jira/Linear Sync: Post Rank/Confidence Change Comment
Given existing issues are impacted by rank/confidence changes When the commit completes Then a comment is added to each impacted issue using the template: "[EchoLens] Queue v{queueVersionId}: Rank {oldRank} -> {newRank} ({delta}), Confidence {oldConf} -> {newConf} ({delta}); Scenario {scenarioId} — See details: {auditLogUrl}" And the comment includes a deep link back to EchoLens And no duplicate comment is posted if the exact comment content already exists on the issue And the audit log records commentCreated=true per issue
Create Tasks for Newly Promoted Themes (Config-Driven)
Given organization settings enable task creation for promoted themes And the diff promotes one or more themes above the creation threshold When the commit completes Then a new Jira/Linear issue is created per promoted theme with summary, description, labels, assignee (if configured), and a backlink to EchoLens And creation is idempotent using externalId=EchoLens:{themeId}:{queueVersionId}; no duplicate issues are created on retries And created issues are linked back to the originating theme in EchoLens And the audit log lists created issue keys/ids
End-to-End Rollback on Failure
Given the platform applies queue changes but a subsequent Jira/Linear sync step fails (non-2xx or timeout beyond retry policy) When the failure is detected Then the live queue is rolled back to the previous version and the queueVersionId associated with the failed commit is invalidated And no external system ends in a modified state; if any side effect occurred, compensating actions revert it or the commit is marked 'failed with compensation applied' and flagged for manual review And a failure audit log entry is written with error codes, failed resource ids, and correlationId And the initiator receives a failure notification with actionable next steps
Success Notification with Deep Links
Given a commit completes successfully When notifications are dispatched Then the initiator (and configured watchers) receives an in-app and/or Slack/email notification within 10 seconds And the notification includes: queueVersionId, scenarioId, counts of themes changed/created/updated, and links to (a) the audit log entry, (b) a filtered queue view of affected themes, and (c) each created/updated Jira/Linear issue And counts and links in the notification match the audit log exactly And clicking any link opens the correct destination

Tone Mirror

Auto-matches reply tone to the customer’s sentiment, role, and brand voice guidelines. Keeps messages empathetic and consistent—calming escalations, improving CSAT, and saving agents from rewriting drafts.

Requirements

Sentiment and Role Classifier
"As a CX agent, I want the system to automatically determine a customer’s sentiment and role so that my replies can match their emotional state and expectations without manual triage."
Description

Real-time service that identifies customer sentiment (from very negative to very positive), urgency, and persona/role (e.g., admin, executive, end-user, developer) from incoming messages and metadata such as channel, account tier, SLA, and CRM attributes. Aggregates multi-message context within a thread, handles sarcasm and mixed sentiment at sentence level, and outputs normalized labels with confidence scores and token-level spans. Exposes an internal API for the Tone Mirror pipeline and enriches EchoLens tickets/chats with tone metadata to enable downstream tone matching and prioritization.

Acceptance Criteria
Real-time sentiment classification per incoming message
Given an English customer message ≤ 2000 characters with metadata present, When the message is submitted to the classifier API, Then the response includes sentiment_label ∈ {very_negative, negative, neutral, positive, very_positive} and sentiment_confidence ∈ [0,1]. And Then token_level_spans are returned with character offsets [start, end) and sentiment tags covering ≥ 90% of sentiment-bearing tokens with IoU ≥ 0.7 against ground truth on the validation set. And Then macro-F1 for sentiment_label ≥ 0.85 and per-class F1 ≥ 0.78 on the held-out benchmark of ≥ 5000 annotated messages. And Then p95 end-to-end latency per single message request ≤ 150 ms at 50 RPS and p99 ≤ 300 ms. And Then confidence calibration ECE ≤ 0.05 on the same benchmark.
Urgency detection using content and metadata
Given a message with SLA, account_tier, channel, and CRM attributes, When classified, Then response includes urgency_label ∈ {low, medium, high, critical} and urgency_confidence ∈ [0,1]. And Then metadata influence rules apply: active SLA breach window ≤ 4h or account_tier ∈ {Enterprise, Platinum} increases urgency by at least one level unless content-only score is already critical. And Then macro-F1 for urgency_label ≥ 0.80 on the urgency benchmark (≥ 3000 items) and precision for 'critical' ≥ 0.85 with recall ≥ 0.75. And Then outputs are deterministic for identical inputs (text + metadata + model_version). And Then the incremental latency for urgency computation adds ≤ 20 ms to the sentiment step at 50 RPS.
Persona/role identification across channels
Given an incoming message with CRM/account attributes and channel, When classified, Then response includes role_label ∈ {admin, executive, end_user, developer, unknown} and role_confidence ∈ [0,1]. And Then if CRM role is present and mapped with confidence ≥ 0.70, it overrides model inference; otherwise model inference applies with channel priors. And Then macro-F1 for role_label ≥ 0.80 overall and per-class F1 for 'executive' ≥ 0.80 on the role benchmark (≥ 3000 items). And Then titles containing any of {CEO, COO, CTO, CFO, VP, Founder} map to executive when no conflicting evidence exists. And Then outputs include normalized role labels consistent across channels for the same user_id in ≥ 95% of cases over a 24h window.
Sentence-level mixed sentiment and sarcasm handling
Given a multi-sentence message, When classified, Then each sentence has a sentence_sentiment_label with token spans and the overall_sentiment_label is produced via documented aggregation. And Then on the mixed-sentiment benchmark (≥ 1500 items), sentence-level macro-F1 ≥ 0.80 and overall label accuracy ≥ 0.85. And Then on the sarcasm subset, F1 for detecting sarcastic negative intent ≥ 0.70 and ≥ 70% of sarcastic cases are surfaced as mixed or negative overall. And Then phrases with positive surface form but negative intent (e.g., "Great, another outage.") are labeled overall ∈ {negative, very_negative} with confidence ≥ 0.60. And Then token/sentence character offsets align to the original text with ≤ 1 character deviation at boundaries.
Thread-level aggregation and updates
Given a thread_id with ≥ 2 messages within a 72h window, When a new message arrives, Then thread_sentiment_label, thread_urgency_label, and thread_role_label are recomputed using all messages and returned with confidences. And Then an event thread_update is emitted to the Tone Mirror pipeline within 2 seconds p95 of message ingestion. And Then on the multi-message benchmark (≥ 200 threads), thread-level macro-F1 for sentiment improves by ≥ 5 points over the single-message baseline and urgency macro-F1 improves by ≥ 3 points. And Then duplicate delivery of the same message_id results in identical outputs and no duplicate events (idempotency). And Then if all messages are neutral, the thread_sentiment_label is neutral with confidence ≥ 0.80.
Internal API contract and performance
Given an authenticated mTLS service request, When POST /v1/classify is called with a valid payload, Then the response conforms to the OpenAPI schema including labels, confidences, sentence_spans, token_spans, thread_id, and model_version. And When batch classifying up to 50 messages, Then p95 latency per item ≤ 250 ms and throughput sustains 100 RPS with error rate < 0.1% over 10 minutes. And Then unauthenticated requests return 401, invalid payloads return 400 with machine-readable errors, and timeouts return 503 with a Retry-After header. And Then the API is versioned; non-breaking changes do not require client updates and breaking changes increment the major version. And Then monthly availability ≥ 99.9% and each response includes request_id correlating to downstream events.
EchoLens record enrichment and downstream availability
Given an ingested ticket/chat, When classification completes, Then the EchoLens record is enriched with sentiment_label, urgency_label, role_label, confidences, sentence_spans, token_spans, thread_labels, and model_version and is queryable within 500 ms p95. And Then the Tone Mirror pipeline receives the enriched payload on the internal bus with the same request_id within 500 ms p95 and can fetch the record by that id. And Then prioritization jobs can sort queues by urgency_label such that 'critical' ranks above 'high' in ≥ 99% of cases; ties are broken by confidence descending. And Then updates after new thread messages propagate to the record and pipeline consumers within 5 seconds p95. And Then all enriched fields are included in outbound sync payloads to downstream systems that request tone metadata.
Brand Voice Guidelines Manager
"As a CX lead, I want to configure and version our brand voice rules so that agents’ replies remain consistent and compliant across all channels."
Description

Admin experience and backend to define and manage brand voice profiles including tone attributes (warmth, formality, confidence, empathy), writing rules, banned phrases, compliance notes, and channel-specific presets. Supports multiple brands/tenants and segment overrides (e.g., enterprise vs SMB), with versioning, approval workflow, and instant propagation to the Tone Mirror engine. Provides import from existing style guides, a preview sandbox with sample inputs, and role-based access control with audit logs.

Acceptance Criteria
Create Brand Voice Profile with Channel Presets
Given an Admin in tenant T with permission "BrandVoice:Manage" is on the New Profile form When they submit a profile with name, tone attributes (warmth, formality, confidence, empathy) each between 0 and 100, at least 1 writing rule, at least 1 banned phrase, compliance notes ≤2000 characters, and channel presets defined for Email, Chat, and Social Then the profile is created with a unique ID, status "Approved", and all fields persisted And the API responds 201 with the created object including timestamps and tenant/brand IDs And retrieving the profile by ID returns identical values with numeric attributes clamped to [0,100] And if any required field is missing or invalid, the API responds 422 with field-level validation errors And any channel preset field not explicitly set inherits from the base profile value
Segment Overrides per Brand
Given a brand has a base profile and two segment overrides (Enterprise, SMB) When the Tone Mirror resolution API is called with context {brand: B1, segment: Enterprise, channel: Chat} Then the resolved profile equals Base ⊕ Enterprise override ⊕ Chat preset from the Enterprise override (with override precedence over base) And when called with context {brand: B1, segment: SMB, channel: Email}, the resolved profile uses SMB override with Email preset And when called with context {brand: B1, segment: Unknown, channel: Social}, the resolved profile falls back to Base + Social preset And resolution completes in ≤150ms p95 for 10k requests with cache warm And requesting a segment not configured returns 404 if strict mode is enabled, else falls back with a warning header
Versioning, Approval Workflow, and Instant Propagation
Given an Editor creates changes to a profile, a new Draft version vN is created and cannot be used for inference When an Approver approves vN Then vN becomes the new Active version and the prior version vN-1 is archived immutable (read-only) And the Tone Mirror engine fetches and uses vN within ≤5 seconds of approval for new requests And GET /profiles/{id}?active=true returns vN And approval action is recorded in audit logs with actor, timestamp, version from/to, and change summary And only users with role Approver can approve or reject; attempts by others return 403
Import Style Guide Mapping
Given an Admin uploads a style guide file (.docx, .pdf, or .md) ≤20MB When the import runs Then the system extracts candidate tone attributes, writing rules, banned phrases, compliance notes, and suggested channel presets into a Draft profile And a mapping review shows all extracted items with source locations and confidence scores And the user can accept, edit, or discard each item and proceed to create the Draft And unsupported formats or parse failures return a clear error with correlation ID and no data persisted And typical imports complete in ≤60 seconds p95 for files ≤20MB
Preview Sandbox Tone Simulation
Given a user selects a profile and inputs a sample customer message with sentiment and role plus a draft agent reply When they click Preview for a selected channel Then the sandbox shows the resolved tone attributes, applied writing rules, and any flagged banned phrases with suggested rewrites And the generated example reply adheres to banned phrase exclusions and reflects channel-specific presets And switching channels updates the preview to the correct resolved profile And preview generation latency is ≤2000ms p95 And no preview actions change the saved profile unless explicitly saved
RBAC and Audit Logging
Given roles Admin, Editor, Approver, and Viewer are defined When each role attempts permitted and non-permitted actions Then Admin can create/edit/delete profiles, manage roles, approve/reject, import/export; Editor can create/edit Drafts but cannot approve or delete Approved; Approver can approve/reject but cannot change role assignments; Viewer has read-only access And unauthorized actions return 403 and do not change state And every create/update/delete/approve/reject/import/export is logged with actor, tenant/brand, profile ID, action, timestamp, and before/after diffs (excluding secrets) And audit logs are immutable, queryable by actor/action/date/profile, and exportable as CSV and JSON
Multi-Tenant Isolation and API Scoping
Given two tenants A and B each with at least one brand and profile When a user or API token scoped to tenant A requests any resource from tenant B Then the system returns 403 and no data is leaked in responses or metadata And search, lists, and exports only return resources within the caller's tenant and brand scopes And cross-tenant IDs are non-enumerable and not guessable (UUIDv4 or equivalent) And bulk operations (import/export) are restricted to the caller's tenant scope And tenancy scope is included in every audit log entry
Adaptive Tone Rewriter
"As an agent, I want one-click rewrites that match the customer’s mood and our voice so that I can send empathetic, on-brand responses faster."
Description

Text generation layer that rewrites agent drafts or creates reply snippets aligned to detected sentiment, recipient role, and selected brand voice profile. Enforces hard guardrails (no promises, no blame, inclusive language) and soft style nudges while preserving facts, variables, and links. Supports selection-based rewrites, sentence-level edits, and multiple suggestion options with rationales and confidence. Meets latency targets (<500 ms for short edits), offers offline-safe fallbacks with heuristic rules, and logs user selections for continuous improvement.

Acceptance Criteria
Tone, Role, and Brand Voice Alignment for Short Draft
Given an agent draft ≤ 200 characters with detected sentiment = Negative, recipient role = Executive, and selected brand voice = Support-Formal When the agent requests a rewrite Then the rewritten text is classified by the tone classifier as Formal-Empathetic with confidence ≥ 0.80 And the message includes an acknowledgment phrase without blame (e.g., “I understand the impact”) and avoids slang And the Flesch-Kincaid grade level is between 8 and 11 inclusive And the rewrite introduces no commitments or timelines not present in the original
Hard Guardrails Enforcement: No Promises, No Blame, Inclusive Language
Given any input draft When the system generates a rewrite Then the output contains 0 matches from the Promises blocklist v1.0 (e.g., “guarantee”, “will definitely”, “promise to”, “ensure that” as commitment) And contains 0 matches from the Blame blocklist v1.0 (e.g., “your fault”, “you should have”, “you did not”) And passes the Inclusive Language Linter with 0 critical violations And contains 0 instances of accusatory second-person constructions (pattern: pronoun “you” + negative verb) as detected by the parser
Preservation of Facts, Variables, and Links
Given a draft containing placeholders (e.g., {ticket_id}, {{customer_name}}), variables (e.g., $orderNumber), URLs, and numeric facts When the system generates a rewrite Then all placeholders and variables are preserved verbatim and unaltered And all URLs are identical to the source and remain clickable And all numeric values are unchanged And the rewrite introduces no new factual entities (products, dates, order numbers) not present in the source
Scoped Rewrite Modes: Selection and Sentence-level
Given a user highlights a selection from character index i to j in a draft When "Rewrite selection" is applied Then only characters in the range [i, j) are modified and all characters outside the range remain byte-for-byte identical Given sentence-level edit mode is enabled When the user selects “Improve sentence” on a specific sentence Then the system returns an edited version of that sentence only And includes an explanation string ≤ 140 characters describing the change
Multiple Suggestions with Rationales and Confidence
Given a draft or selection When the user requests suggestions Then the system returns exactly 3 distinct suggestions And each suggestion includes a rationale string ≤ 200 characters And each suggestion includes a confidence score in [0, 1] with two-decimal precision And suggestions are ordered by confidence descending And pairwise Jaccard similarity over word unigrams between any two suggestions ≤ 0.85
Performance and Resilience: Latency Targets and Fallback
Given short edit requests (input length ≤ 200 characters) under normal load (API queue wait P95 ≤ 50 ms) in the primary region When processing 1,000 consecutive requests Then end-to-end latency P95 ≤ 500 ms and P99 ≤ 700 ms, with failure rate (5xx/timeout) ≤ 0.5% Given the LLM provider errors or times out (> 800 ms) When a rewrite is requested Then a heuristic fallback rewrite is returned within 800 ms P95 And the UI displays a non-blocking “Fallback active” notice And the fallback output satisfies the Hard Guardrails Enforcement criterion
Telemetry: Logging User Selections with Privacy Controls
Given suggestions are displayed to the user When the user accepts, edits, or rejects a suggestion Then the system logs event_type, suggestion_id, confidence, rationale_hash, latency_ms, anonymized user_id, and org_id And full message content is excluded from logs unless org setting training_opt_in = true And logs are delivered to analytics with ≥ 99% success within 5 minutes And PII redaction rules v1.2 report 0 critical findings in weekly audits
Real-time Tone Coach in Composer
"As an agent, I want live tone guidance in my reply box so that I can adjust my message before sending without leaving my workflow."
Description

Embeddable composer extension (SDK and widgets) for Zendesk, Intercom, Front, email, and EchoLens UI that displays a live tone meter, role hints, and inline suggestions as the agent types. Offers quick tone presets mapped to brand voice (e.g., Calm, Reassuring, Confident), hotkeys for apply/undo, and transparent explanations for suggestions. Supports channel-aware formatting, privacy controls to process only selected text, and event hooks with telemetry for adoption tracking.

Acceptance Criteria
Live Tone Meter and Role Hints While Typing
Given the composer is focused in a supported host (Zendesk, Intercom, Front, Email, EchoLens UI) When the agent types or edits text by at least 1 character Then the tone meter recalculates and visually updates within 250 ms after the keystroke and reflects the latest draft state Given labeled sentiment fixtures (escalated/negative, neutral, positive) and known customer roles (e.g., VIP, Billing Admin) When each fixture is pasted or typed into the composer with the role metadata present Then the meter label/color maps to the expected target tone per brand voice configuration for that sentiment-role pair Given customer role metadata is available When the composer loads or role metadata changes Then a role hint badge appears within 300 ms showing the correct role and one-line guidance from brand rules; and when no role metadata is available, the badge is hidden Given the SDK fails to reach the tone service or times out When the agent types Then the meter enters a safe "Unavailable" state within 500 ms and no blocking occurs in the composer
Inline Suggestions with Transparent Explanations
Given the agent's draft deviates from the brand tone threshold When the system generates inline suggestions Then suggestions appear inline adjacent to the relevant sentence, are non-destructive until applied, and a preview diff is available on hover or click Given a suggestion is visible When the agent clicks Apply Then only the relevant text segment is transformed, cursor position is preserved, and the change is completed within 200 ms Given a suggestion is visible When the agent clicks "Why this?" Then an explanation modal/panel opens within 200 ms showing: rule name/ID, tone dimension (e.g., Empathy, Brevity), detected issue, recommended change, and model confidence (0–100%) Given internal tone evaluation is available When a suggestion is applied on test fixtures Then the tone alignment score increases by at least 20% relative to baseline for that segment
Quick Tone Presets and One-Click Undo
Given brand voice presets are configured (e.g., Calm, Reassuring, Confident) When the composer loads Then preset buttons are visible and labeled per configuration in the toolbar or widget within 500 ms Given text is selected When the agent applies a preset Then only the selected text is rewritten to match the preset, URLs/email addresses/placeholders (e.g., {{ticket.id}}) remain unchanged, and formatting is preserved per channel rules Given a preset has been applied When the agent clicks Undo in the widget Then the text reverts to its immediately prior state within 200 ms and the action is logged in the session history Given multiple presets are applied sequentially When Undo is triggered repeatedly Then each prior state is restored in order up to the last five actions
Hotkeys for Apply and Undo
Given default hotkeys are enabled (e.g., Apply suggestion: Ctrl/Cmd+Enter; Undo last action: Ctrl/Cmd+Z; Cycle presets: Ctrl/Cmd+Shift+P) When the composer has focus Then pressing each hotkey triggers the corresponding action within 150 ms without opening host app shortcuts Given the host app reports a conflicting shortcut via the SDK API When the widget initializes Then the extension does not intercept the conflicting key and presents a rebind prompt allowing the agent to choose an alternative that takes effect immediately Given the composer is not focused When hotkeys are pressed Then no actions are triggered by the extension
Channel-Aware Formatting Compliance
Given the host channel is HTML email (e.g., Zendesk email composer) When a suggestion or preset is applied Then HTML structure (paragraphs, links, bold/italic) is preserved, and only textual content changes within tags Given the host channel is chat/Markdown (e.g., Intercom, Front chat) When a suggestion or preset is applied Then output uses Markdown formatting (no raw HTML), preserves line breaks, and escapes special characters as needed Given the host channel is plain-text email When a suggestion or preset is applied Then output contains no markup, preserves line breaks, and wraps long lines at the host default without inserting extra whitespace Given any channel When content contains snippets/macros/placeholders Then these tokens are left intact and not reformatted
Privacy Controls: Process Only Selected Text
Given privacy mode is enabled by policy When no text is selected Then the widget displays a non-blocking prompt to select text, and no draft content leaves the client Given privacy mode is enabled and text is selected When tone analysis or rewriting occurs Then only the selected text (with client-side redaction of emails, phone numbers, credit card numbers, and ticket IDs) is sent for processing and no full-thread content is transmitted Given privacy mode is enabled When telemetry/logs are emitted Then no raw message content is included; only hashed identifiers and aggregate metrics are sent Given privacy mode is disabled by an admin override When analysis runs Then the system processes the full draft as configured and displays a visible "Org policy: Full Draft" indicator
Telemetry Event Hooks for Adoption Tracking
Given the SDK is initialized with telemetry enabled When the widget renders and the meter updates Then a tone_meter_updated event is emitted with timestamp, host_app, channel, tenant_id, agent_id_hash, and model_version; no message content is included Given suggestions are shown/applied or presets/hotkeys are used When each action occurs Then corresponding events (suggestion_shown, suggestion_applied, preset_applied, hotkey_used, explanation_opened) are emitted within 50 ms, batched if needed, and delivered reliably with retry/backoff Given telemetry is disabled via admin or workspace opt-out When the widget operates Then no events are emitted or queued Given a consumer registers an event hook When events fire Then the hook callback executes asynchronously (non-blocking) and returns within 20 ms, or is skipped if it exceeds the timeout
Confidence Thresholds and Safety Filters
"As a CX manager, I want the system to avoid risky rewrites and default to safe templates when uncertain so that we protect customers and the business."
Description

Policy and safety layer that enforces minimum confidence for sentiment/role detection and rewrite suitability before applying changes. Falls back to neutral, human-authored templates when confidence is low or content is sensitive (e.g., legal threats, medical issues, PII). Includes profanity/escalation detectors, compliance checkers, and regional data handling rules. Provides admin-tunable thresholds per channel and jurisdiction and emits explanations and flags for auditability.

Acceptance Criteria
Low-Confidence Sentiment Detection Fallback
Given a customer message is ingested on a supported channel and the sentiment or role detection confidence is below the admin-configured threshold for that channel and jurisdiction When Tone Mirror prepares a reply draft Then the system uses the neutral, human-authored template for the detected customer role and channel And no model-driven tone rewrite is applied And an audit event with type "fallback_low_confidence" is recorded containing message_id, model_confidences, applied_thresholds, channel, jurisdiction, and policy_version And the agent UI displays a non-blocking notice that a fallback was applied
Sensitive Content Safety Filter (Legal/Medical/PII)
Given a message matches any sensitive-content detector (legal threat, medical inquiry, or PII per policy patterns and dictionaries) When Tone Mirror processes the draft Then no customer text is paraphrased or echoed back by the model And the reply uses the neutral de-escalation template for the channel And detected PII is masked in previews, logs, and exports And an audit event "sensitive_content_block" records detector_type, redaction_summary, and policy_version And the draft requires explicit agent confirmation before send (auto-send disabled)
Per-Channel and Jurisdiction Threshold Application
Given admin thresholds are configured as sentiment>=0.82, role>=0.90, rewrite>=0.88 for Email in EU And a message yields sentiment=0.85, role=0.87, rewrite=0.91 When processing the message Then rewrite is not applied because role<0.90 And the neutral template is used And the audit event records applied_thresholds EU-Email and the failing dimension "role"
Profanity and Escalation Detection Handling
Given a message contains profanity and the escalation score >= the configured escalation_threshold When composing the reply Then the reply tone is set to "calming" per brand voice guidelines and profanity is never mirrored And a standardized de-escalation opener from the approved library is included And if escalation_score >= handoff_threshold, auto-send is disabled and the draft is routed for manual review And an audit event "escalation_handled" includes escalation_score, thresholds, and selected strategy
Admin Thresholds Editing and Versioning
Given an admin updates confidence thresholds for one or more channel/jurisdiction pairs When saving the policy Then values must be in [0.0,1.0] with up to 2 decimal places and rewrite_threshold >= max(sentiment_threshold, role_threshold) And the change is versioned with version_id, editor, timestamp, and changelog note And the new version applies to drafts created >= 60 seconds after save And drafts created before that continue using their original policy_version
Explanations and Auditability
Given a draft is produced with either rewrite applied or fallback When the agent opens the "Decision Details" Then the UI shows per-dimension confidences, thresholds, detector hits, channel, jurisdiction, decision, and policy_version And a human-readable explanation <= 240 characters maps to the recorded flags And the audit export API returns NDJSON for a requested date range with the same fields, max 10,000 records per request, and supports pagination via cursor
Regional Data Handling Enforcement
Given a customer record is tagged jurisdiction=EU When Tone Mirror runs detection, rewrite evaluation, and logging Then all processing occurs within EU-designated infrastructure and no personal data leaves the region And PII logs store salted hashes of matched tokens, not raw values And if regional compliance checks fail, the request is aborted with error_code "region_policy_blocked" and no data is persisted And an audit event "region_enforced" is recorded with region_id and check_result
Tone Impact Analytics
"As a CX lead, I want to understand how tone-matched replies affect CSAT and escalations so that I can optimize our voice guidelines and training."
Description

Analytics module that correlates Tone Mirror usage with outcomes such as CSAT, first-response time, escalation rate, and reopen rate. Breaks down performance by channel, segment, agent, and voice profile version, and supports A/B experiments to evaluate tone strategies. Surfaces top effective patterns and failure cases, integrates with EchoLens theme map to show impact on high-priority themes, and exports metrics to BI tools with privacy-preserving aggregation.

Acceptance Criteria
Outcome Correlation by Tone Mirror Usage
Given a selected date range and channel When the user views the Outcome Correlation dashboard Then the system displays CSAT, first-response time, escalation rate, and reopen rate for interactions where Tone Mirror was used vs not used in the same period Given each cohort has ≥ 100 interactions When metrics are rendered Then the UI shows absolute values, deltas, 95% confidence intervals, and a significance indicator (p < 0.05) per metric; otherwise shows “Insufficient data” per metric Given the user changes the date range, channel, or timezone When the dashboard reloads Then values recompute within 3 seconds for cohorts ≤ 100k interactions
Dimensional Breakdown by Channel, Segment, Agent, and Voice Profile Version
Given filters for channel, customer segment, agent, and voice profile version When the user applies any combination Then the table pivots metrics and deltas per selected dimensions with correct row totals and grand totals Given an agent row is clicked When navigating to detail Then the view shows that agent’s per-metric results with a list of sample interactions (min 20, redacted) matching the filters Given ≥ 1M interactions in scope When querying Then the system returns results within 8 seconds or displays a progress bar with a cancellable state
A/B Experiment Configuration and Uplift Readout
Given a new experiment When the user defines variants (e.g., Tone Profile A vs B) and randomization rules Then the system assigns traffic split as configured (10–90% granularity) and records assignment IDs Given an experiment reaches the minimum sample size per variant (≥ 500 interactions) When the user opens the readout Then the UI shows uplift for CSAT, first-response time, escalation rate, and reopen rate with 95% CI, p-values, and current power Given guardrail metrics are breached (e.g., escalation rate +3% absolute with p < 0.05) When detected Then the system triggers an alert and recommends a pause with a one-click stop action
Top Effective Tone Patterns and Failure Cases
Given tone features (e.g., empathy score, mirroring level, formality) extracted from replies When computing associations Then the system ranks top 10 patterns linked to positive outcome deltas and top 10 linked to negative deltas, each with support ≥ 200 and effect size reported Given a pattern card is opened When viewing details Then the UI shows example anonymized messages, affected segments/channels, and the estimated contribution to outcome change Given a user flags a pattern as irrelevant When submitted Then the pattern is suppressed from summaries for that workspace and feedback is logged for model retraining
Theme Map Impact Overlay
Given the EchoLens theme map is open When the user toggles “Tone Impact” Then each theme node displays outcome deltas attributable to Tone Mirror usage in that theme’s interactions plus confidence indicators Given a high-priority theme is selected When drilling down Then the panel shows top effective tone patterns within that theme and links to representative tickets/chats with redactions Given filters in Analytics are changed When returning to the theme map Then the overlay remains synced to the same filters and date range
Privacy-Preserving BI Export
Given BI export is configured for BigQuery or Snowflake When a daily export runs Then the system delivers aggregated tables only (no raw text or PII), with k-anonymity k ≥ 10 per row and optional Laplace noise ε = 1 applied to counts where needed Given an export job completes When the user checks the audit log Then start/end time, row counts, schema version, and destination are recorded and downloadable Given a query would violate privacy thresholds When preparing the export Then the system suppresses the row and increments a suppression counter surfaced in the export report
Data Freshness and Latency
Given a customer interaction is closed and Tone Mirror usage is recorded When outcomes are ingested Then analytics aggregates update within 15 minutes and the freshness badge shows “< 15m” Given an A/B experiment is active When nightly recomputation runs Then cumulative stats reflect all data up to 02:00 UTC with a visible watermark timestamp Given data pipeline failures occur When detected Then the UI shows a degraded state with affected metrics greyed out and a status banner linking to incident details

Live ETA Sync

Continuously pulls status and target windows from Jira/Linear to insert safe, range-based ETAs and automatic updates if timelines shift. Prevents overpromising, builds trust, and closes loops without manual chasing.

Requirements

Jira/Linear OAuth Connectors & Project Mapping
"As a workspace admin, I want to connect our Jira/Linear projects to EchoLens so that ETAs are derived directly from the source of truth without manual copying."
Description

Establish secure, least-privilege OAuth integrations with Jira and Linear to ingest issue status, target dates/milestones, and release windows. Support workspace selection and project/team mapping so EchoLens themes link to the correct upstream issues. Handle permission scopes, token refresh, and revocation. Provide a guided setup flow with field selectors (e.g., Jira Due Date, Fix Version dates; Linear Target Date/Cycle) and validation. Ensures Live ETA Sync has authoritative, structured inputs while maintaining admin control and data minimization.

Acceptance Criteria
Least-Privilege OAuth Consent for Jira and Linear
Given an EchoLens org admin initiates a Jira or Linear connection When the provider consent screen is presented Then the requested scopes are read-only and limited to issues, projects/teams, versions/releases, and metadata (no write/admin/PII scopes) Given the connection is completed When EchoLens calls a write-only or admin endpoint Then the provider blocks the call (HTTP 401/403) and no data is modified Given the connection is completed Then access and refresh tokens are stored encrypted at rest and are not exposed in client-side code or logs
Workspace/Site and Project/Team Mapping Selection
Given the admin has access to multiple Jira sites or Linear workspaces When connecting Then the admin can select exactly one site/workspace and one or more projects/teams to map Given the mapping is saved Then EchoLens persists provider IDs for the site/workspace and projects/teams and displays the mapping in settings Given the mapping is changed When the admin updates selections Then subsequent data pulls are limited to the new mapping and previously unmapped projects are excluded within 5 minutes
Field Selector Configuration for ETA Sources
Given Jira is selected When configuring fields Then the admin can choose Due Date, Fix Version release date, or a specific custom date field and validation confirms availability in each selected project (or prompts for per-project overrides) Given Linear is selected When configuring fields Then the admin can choose Target Date or Cycle (start/end) and validation confirms availability in each selected team Given validation runs When any selected field is unavailable for a mapped project/team Then setup cannot be completed and inline errors identify the exact project/team; a test fetch returns at least one sample item per mapped project/team before enabling
Automatic Token Refresh and Resilience
Given an active connection with a refresh token When the access token is within 5 minutes of expiry Then EchoLens refreshes the token without user action and continues data pulls Given transient provider errors (HTTP 429/5xx) Then EchoLens retries with exponential backoff up to 3 attempts and surfaces connector health status Given refresh permanently fails (e.g., invalid_grant) Then the connector status changes to Disconnected, pulls stop, and the admin receives an in-app banner and an email notification within 10 minutes
Revocation and Scope Change Detection
Given the admin revokes the app or reduces scopes in Jira/Linear Then EchoLens detects the change within 15 minutes (or on next poll), disables pulls, marks the connector Disconnected, and displays remediation steps Given the connector is disconnected due to revocation Then stored tokens are purged, and an audit log entry is recorded with timestamp, actor (if available), provider, and reason
Scoped Data Ingestion and Data Minimization
Given mapping and field selectors are configured When EchoLens ingests data Then only issues from mapped projects/teams are fetched and only status, key/identifier, summary, and selected ETA fields (due date / release window / cycle dates) are stored; no comments, attachments, or user PII are persisted Given provider rate limits Then ingestion uses delta sync since the last successful run and maintains a steady-state error rate with no recurring HTTP 429 responses
Theme-to-Issue Linking Validation
Given a user attempts to link an EchoLens theme to a Jira/Linear issue When the issue is not in a mapped project/team Then the link is blocked with an explanatory error and no association is stored Given a user links a valid issue within the mapping Then the association is saved exactly once (no duplicates), and subsequent syncs update the theme with the issue’s current status and ETA fields within 5 minutes
Real-time Sync Orchestrator (Webhooks + Polling)
"As a product manager, I want upstream changes to reflect in EchoLens within minutes so that the ETAs my team shares are always current."
Description

Implement a resilient sync pipeline that subscribes to upstream webhooks where available and falls back to incremental polling on a configurable cadence. Deduplicate events, batch updates, and respect API rate limits. Include retry with exponential backoff, circuit breakers, and dead-letter handling. Maintain per-project cursors and a health dashboard so PMs can verify freshness. Guarantees timely ETA updates across EchoLens with minimal lag and predictable behavior under load or upstream outages.

Acceptance Criteria
Webhook-First Sync with Automatic Polling Fallback
Given a project with Jira/Linear webhooks configured and healthy When an upstream ETA-related change event is emitted Then the orchestrator ingests the event within 10 seconds and EchoLens reflects the updated ETA within 30 seconds end-to-end (p95) Given webhooks are failing or absent for a project for more than 2 minutes When no webhook events are received during that window Then the orchestrator enables incremental polling within 30 seconds at the configured cadence (default 60s) without duplicating updates Given webhook delivery has recovered When the system observes continuous healthy deliveries for 5 minutes Then polling for that project is disabled and webhook-only operation resumes automatically
Incremental Polling with Per-Project Cursors
Given polling is active for a project When fetching changes from Jira/Linear Then the request includes the stored cursor/updatedSince token to only retrieve items modified after the last committed checkpoint Given a polling cycle processes a batch successfully When committing results Then the cursor is advanced atomically to the last processed item; on partial failure, the cursor remains at the last safe checkpoint Given the orchestrator restarts unexpectedly When it resumes operation Then it loads the persisted cursor for each project and avoids data loss while preventing reprocessing beyond idempotent safeguards
Event Deduplication and Idempotent Processing
Given multiple webhook and/or polling events for the same issue are received within a short interval When events share the same upstream event ID or identical content hash Then only one update is applied to EchoLens and the rest are discarded as duplicates Given upstream retries the same webhook delivery When the orchestrator processes it Then the operation is idempotent and results in no additional ETA changes or side effects Given two valid events for the same resource contain conflicting ETA values When both are processed Then the event with the latest upstream updatedAt timestamp deterministically overwrites the older one
Batching and API Rate Limit Compliance
Given more than 50 pending updates for a tenant When issuing requests to Jira/Linear Then updates are sent in batches of at most 50 items (configurable), with an inter-batch flush interval that keeps request rates under the documented per-tenant limits Given upstream responds with HTTP 429 and a Retry-After header When handling the response Then the orchestrator delays retries per Retry-After and schedules remaining work without dropping or duplicating tasks Given a 15-minute soak at a target throughput of 20 updates/second per tenant When monitoring upstream responses Then the system records zero unhandled 429s and no upstream bans while completing 99% of queued updates within the test window
Retry, Exponential Backoff, and Circuit Breaker
Given a transient error (network timeout, HTTP 5xx) occurs on an upstream call When retrying the operation Then the orchestrator retries up to 5 times with exponential backoff (base 1s, max 60s) and full jitter before marking the attempt as failed Given the rolling 1-minute failure rate for an endpoint exceeds 50% When evaluating breaker conditions Then the circuit breaker opens for 2 minutes, halting upstream calls, queuing new tasks, and serving cached status where applicable Given the circuit breaker is open When two consecutive health probes succeed during half-open state Then the breaker closes and normal traffic resumes All retries and breaker state transitions are recorded with timestamps and exposed on the health dashboard
Dead-Letter Queue and Operator Replay
Given a message exhausts max retries or fails validation (e.g., schema mismatch) When finalizing processing Then the message is moved to a dead-letter queue with payload snapshot, error details, and last-attempt timestamp Given items reside in the dead-letter queue When an operator triggers a replay for selected items Then the orchestrator reprocesses them idempotently and updates their status, without creating duplicate updates in EchoLens DLQ items are retained for at least 14 days, and an alert is emitted if DLQ size exceeds 10 items or any item remains unresolved for more than 24 hours
Health Dashboard Freshness and SLA Visibility
The health dashboard presents, per project: last successful sync timestamp, webhook status (healthy/degraded/down), polling status (on/off/cadence), backlog size, recent error rate, breaker state, and next retry ETA Given normal operation When evaluating freshness Then the indicator is green if last successful sync < 2 minutes ago, yellow if 2–10 minutes, and red if > 10 minutes Given a PM opens a project’s sync details When viewing recent activity Then the dashboard shows the last 100 sync events with timestamps, outcome (success/failure), and cursor values, updating at least every 30 seconds
ETA Range Calculation Engine
"As a CX lead, I want ETAs expressed as clear ranges with confidence so that I can set expectations without overpromising."
Description

Create a rules-driven engine that converts upstream single dates or windows into safe, range-based ETAs. Apply buffers based on EchoLens theme confidence, historical slip rates per team, and issue risk signals (e.g., status transitions, blocked flags). Normalize time zones, support business-day calendars, and format human-friendly ranges (e.g., “Aug 22–29”). Provide guardrails for minimum/maximum window widths and clearly mark low-confidence estimates. Outputs are deterministic and explainable to build trust.

Acceptance Criteria
Single-date to safe ETA range with buffers and low-confidence marking
Given an upstream single target date 2025-09-02T16:00:00Z for issue X on team Alpha, workspace timezone America/Los_Angeles, team Alpha slip rate 25% (trailing 90d), theme confidence 0.55, and risk signals {blocked:true, statusTransitionsLast7Days:4} And configuration {minWindowWidthBD:3, maxWindowWidthBD:20, riskMultipliers:{blocked:+0.15, churnGE3:+0.10}, confidenceWidthAdj:{lt0_60:+0.30, gte0_60_lte0_80:+0.15, gt0_80:+0.05}} And team Alpha calendar is Monday–Friday business days When the engine computes the ETA range Then the output lower and upper bounds are business days in America/Los_Angeles And the lower bound date is on or after 2025-09-02 (local date) And the window width equals 6 business days (ceil(3 * (1 + 0.25 + 0.15 + 0.10 + 0.30)) = 6) And a low-confidence indicator is present because confidence < 0.60 And the explanation lists {baseWidthBD:3, slipRatePct:25, riskBlockedPct:15, riskChurnPct:10, confidenceAdjPct:30, finalWidthBD:6, finalRange:[<lower>,<upper>]}
Window input normalization, proportional buffering, and guardrail enforcement
Given an upstream target window [2025-08-22, 2025-08-29] (UTC dates) for issue Y on team Beta, workspace timezone America/Los_Angeles, team Beta slip rate 10%, theme confidence 0.82, and no risk signals And configuration {minWindowWidthBD:3, maxWindowWidthBD:20, confidenceWidthAdj:{gt0_80:+0.05}} And team Beta calendar is Monday–Friday business days When the engine computes the ETA range Then the base window width equals 6 business days (inclusive business days between Aug 22–29 on team Beta calendar) And the adjusted window width equals 7 business days (ceil(6 * (1 + 0.10 + 0.05)) = 7) And the lower bound is on or after the normalized upstream start date; the upper bound is on or after the normalized upstream end date And guardrails are respected (final width >= 3 and <= 20 business days) And the explanation indicates guardrail status = not applied and lists component contributions and final range
Timezone normalization and DST correctness
Given an upstream single target date 2025-03-10T01:30:00Z for issue Z on team Gamma And workspace timezone America/Los_Angeles with a DST transition on 2025-03-09 And team Gamma calendar is Monday–Friday business days When the engine normalizes timestamps and computes the ETA start date Then the timestamp normalizes to 2025-03-09 18:30 PDT (America/Los_Angeles) And because 2025-03-09 is not a business day, the computation rolls forward to 2025-03-10 as the starting business day And the explanation includes {originalTimestamp:"2025-03-10T01:30:00Z", normalizedLocal:"2025-03-09T18:30:00-07:00", dstRuleApplied:true, rollReason:"non-business day"}
Business-day calendar handling with weekends and holidays
Given team Delta uses a business-day calendar with weekends (Sat/Sun) and a holiday on 2025-11-27 (Thanksgiving) And an upstream window [2025-11-24, 2025-11-28] And workspace timezone America/Los_Angeles, team Delta slip rate 15%, theme confidence 0.70, no risk signals And configuration {minWindowWidthBD:3, maxWindowWidthBD:20, confidenceWidthAdj:{gte0_60_lte0_80:+0.15}} When the engine computes the ETA range Then the base width counts business days only: 4 business days (Nov 24, 25, 26, 28) And the adjusted width equals 6 business days (ceil(4 * (1 + 0.15 + 0.15)) = 6) And if the computed upper bound falls on a weekend or holiday it is moved to the next business day; the explanation notes any roll-forward action And the explanation cites the calendar used and holidays skipped
Human-friendly ETA range formatting
Given a computed ETA in America/Los_Angeles with lower=2025-08-22 and upper=2025-08-29 and locale=en-US When the engine formats the ETA for display Then it outputs "Aug 22–29" (en dash, month abbreviation, no year because it is the current year and same month) And for cross-month same-year ranges it outputs "Aug 29–Sep 5" And for cross-year ranges it outputs "Dec 29, 2025–Jan 6, 2026" And no time components are shown; dates are localized to the selected locale
Deterministic output and explainable audit trail
Given identical inputs, configuration, calendar, and engine version are provided twice for the same issue When the engine computes the ETA range and explanation both times Then the range bounds, window width, low-confidence flag, and formatted string are identical across runs And the explanation includes {engineVersion, inputHash, configHash, calendarId, timezoneId, roundingRule, guardrailActions, components:[{name, valuePctOrBD}] , finalRange} And equality of (range, explanation) is verified via stable hashes in unit tests
Timeline Drift Detection & Auto-Update
"As a product manager, I want ETA changes to propagate automatically when upstream dates move so that I don’t have to manually update every theme and stakeholder message."
Description

Continuously compare previously published ETAs with newly computed outputs from upstream changes to detect drift. Auto-update ETAs across EchoLens surfaces (ranked queue, theme detail, live theme map) and trigger notifications when shifts exceed configurable thresholds. Maintain change reasons and diffs, and throttle noisy flaps. Ensures customers and stakeholders see accurate timelines without manual chasing.

Acceptance Criteria
Sub-Threshold Drift: Silent Auto-Update
Given an item with a previously published ETA on the ranked queue, theme detail, and live theme map And a workspace drift-notification threshold of T minutes When the upstream system changes the ETA and the absolute drift is less than T Then all three EchoLens surfaces display the new ETA within 60 seconds of detection And no user-facing notification is sent And the item shows an Updated timestamp reflecting the new publish time And repeated identical upstream events do not create duplicate updates (idempotent)
Threshold-Exceeding Drift: Notification Trigger
Given an item with a published ETA and designated subscribers (owner, watchers, customer loop participants) And a drift-notification threshold T minutes When the computed ETA changes and the absolute drift is greater than or equal to T Then notifications are sent to in-app, email, and Slack (if configured) within 60 seconds of detection And the notification includes: item identifier, old ETA range, new ETA range, absolute delta (minutes/hours/days), brief reason summary, and a deep link to details And all EchoLens surfaces reflect the new ETA within 60 seconds And duplicate notifications for the same change event are not sent
Cross-Surface Consistency
Given an item appears on the ranked queue, theme detail, and live theme map When a drift update is applied Then the ETA displayed on all three surfaces matches exactly (range bounds, timezone, and formatting) And cached/stale values are invalidated so no surface shows the prior ETA after 60 seconds And public API/exports return the same ETA values as the UI within the same update cycle And search, filters, and sorting use the updated ETA immediately after publish
Change Reason and Diff Logging
Given a drift event occurs for an item When the system updates the ETA Then an audit record is stored with: timestamp, actor/source system, previous ETA range, new ETA range, drift magnitude, upstream fields that changed, computed reason text, and source version IDs And the diff is visible in the item's change history UI And audit records are immutable and queryable via API by item, date range, and drift magnitude And if upstream provides no reason, the record shows Reason: Unknown without blocking the update
Flap Throttling and Coalescing
Given a throttle window W minutes and a per-item notification cap N per 24 hours And upstream changes oscillate the ETA multiple times within W When drift events occur within the same throttle window Then only the latest ETA within W is published to surfaces (intermediate updates are coalesced) And notifications are suppressed except the first and the final consolidated change within W And total notifications per item do not exceed N in a rolling 24-hour period And the change history shows a single consolidated entry with intermediate states collapsed (but retained internally)
Configurable Thresholds and Precedence
Given drift thresholds can be configured at workspace default, project/team, and item override levels When thresholds differ across levels Then item-level override takes precedence over project/team, which takes precedence over workspace default And thresholds accept units (minutes/hours/days) or a percentage of the current ETA window size with server-side validation And changes to thresholds take effect within 5 minutes and are recorded in the audit log
Upstream Failure Handling and Staleness
Given the upstream system (Jira/Linear) is unavailable or times out When a drift detection cycle runs Then the last known ETA remains displayed and a Sync delayed indicator is shown in UI and API metadata And no drift notifications are sent while upstream status is unknown And the system retries with exponential backoff up to M attempts and emits an operational alert after the final failure And upon recovery, updates are applied in order and backfilled into change history with accurate timestamps
Customer-Safe ETA Messaging Templates
"As a CX lead, I want pre-approved templates that auto-insert ETAs so that updates are fast, consistent, and don’t overpromise."
Description

Provide templated copy that inserts range-based ETAs and confidence into customer-facing updates while avoiding absolute commitments. Support variables (product area, channel tone), localization, and audience-specific phrasing. Include fallback messages when data is insufficient and highlight next review checkpoints. Ensures consistent, brand-safe communication that builds trust and closes loops efficiently.

Acceptance Criteria
Safe Range-Based ETA Insertion From Live Sync
Given a work item with ETA window [startDate, endDate] and confidence >= 0.7 and variables {productArea, channel, locale, channelTone} When rendering the "ETA Update" template Then the output includes the date range formatted per locale (e.g., en-US: "MMM d–MMM d, yyyy") And includes a confidence qualifier from the approved lexicon for the channel and locale And contains 0 occurrences of banned absolute-commitment phrases ["guarantee","will definitely","promise","exact","on <date>"] And all provided variables are interpolated with 0 unresolved placeholders And brand-safety validation returns pass=true And the rendered body length is between 120 and 600 characters for channel='Email'
Fallback Messaging When ETA Is Insufficient
Given ETA window is missing OR confidence < 0.5 When rendering any customer-facing update template Then the message uses the fallback variant that omits specific dates And includes a next review checkpoint date within 7 calendar days, formatted per locale And includes a neutral expectation statement using approved lexicon (e.g., "We'll share an update by <date>") And brand-safety validation returns pass=true And 0 unresolved placeholders remain
Variable Interpolation and Validation
Given required variables {productArea, channelTone, audience, locale} are provided When rendering any template Then all variables are interpolated and 0 unresolved or unknown placeholders remain And if any required variable is missing, the renderer applies defaults {productArea:"Product", channelTone:"neutral", audience:"customer", locale:"en-US"} without exposing raw placeholders And invalid tokens are logged with reason codes and do not appear in the output And p95 render time <= 200 ms under 100 concurrent requests
Localization and Date/Time Formatting by Locale
Given customer locale=de-DE and timezone=Europe/Berlin When rendering an ETA message Then the date range is formatted as dd.MM.–dd.MM.yyyy using the customer's timezone And the confidence phrase is selected from the German approved lexicon And if any translation key is missing, the system falls back to en-US while preserving safe phrasing And the output contains 0 banned phrases from the de-DE banned list
Audience- and Channel-Specific Tone and Length
Given audience='Executive' and channel='In-App' When rendering an ETA update Then the message uses the 'concise' variant, <= 280 characters, and includes a link to details Given audience='Customer' and channel='Chat' When rendering an ETA update Then the message uses the 'conversational' variant, <= 240 characters, and may include up to 1 emoji if allowed by locale And for all 'Customer' variants, readability grade <= 8 (Flesch-Kincaid) And for 'Executive' variants, 0 emojis and no exclamation marks
Change Notice Template on Timeline Shift
Given a previously communicated ETA window exists for the issue And a new ETA window differs by >= 2 days OR confidence tier changes (low/med/high) When rendering the "ETA Revision" template Then the message includes both previous and new ranges and an acknowledgment of change using approved lexicon And includes {shiftReason} if provided; else uses neutral rationale And is sent or queued within 30 minutes of detecting the change for supported channels And deduplicates to maximum 1 revision message per conversation per 24 hours
Override, Locking & Audit History
"As a product manager, I want to override an ETA with context and have a history of changes so that I can handle exceptions while remaining transparent."
Description

Allow authorized users to override computed ETAs with a locked manual range, add rationale, and set an expiration or review date. Persist a complete audit trail of ETA computations, overrides, upstream change events, and who/when changes were made. Provide one-click revert and policy controls (e.g., approvals for large deviations). Balances automation with human judgment and accountability.

Acceptance Criteria
Manual ETA Override with Lock and Expiry
- Given a user with ETA_OVERRIDE permission views an item with a computed ETA, When they enter a manual ETA as a valid date range (start_date <= end_date) and a rationale (minimum 10 characters) and set an expiration date, Then the item displays the manual range labeled "Manual Override (Locked)" with the user's name and expiration date. - Given a locked manual override, When automatic syncs run, Then the displayed ETA does not change until the lock expires or is manually released. - Given the override is saved, Then an audit entry is appended with event_type=override_created, before_value=computed_range, after_value=manual_range, rationale, user_id, timestamp_utc. - Given form validation, When start_date > end_date or fields are missing, Then the Save action is disabled and inline errors are shown.
Approval Workflow for Large Deviations
- Given a policy that requires approval when the manual range deviates >30% or >14 days from the computed ETA, When a user submits such an override, Then the override enters "Pending Approval" and is not applied to customer-facing views. - Given a pending request, When an ETA_APPROVER approves, Then the override is applied and locked; When rejected, Then the override is discarded; all actions are logged to the audit trail. - Given a pending request without decision for 48 hours, Then an escalation notification is sent to workspace owners and the request remains unapplied. - Given the override is below the policy threshold, When submitted, Then it is applied immediately and logged without requiring approval.
One-Click Revert to Prior State
- Given an item with an active manual lock, When a user clicks Revert, Then the system restores the last non-manual state (computed or prior approved override), removes the lock, and updates all customer-facing surfaces within 2 minutes. - Then a webhook event "eta.revert.completed" is emitted with item_id, before_value, after_value, actor, timestamp_utc. - Then an audit entry is appended with event_type=revert, before_value=manual_range, after_value=restored_value, user_id, timestamp_utc.
Complete and Tamper-Evident Audit Trail
- Given any ETA-related change (compute update, upstream sync change, override create/update, approval decision, expiry, revert), Then an append-only record is stored with fields: id, event_type, before_value, after_value, actor (user_id or system), rationale (nullable), source, timestamp_utc, correlation_id. - Then records are chained via prev_hash and record_hash to provide tamper evidence; delete/alter operations are blocked; admin redactions produce a new redaction event preserving original metadata. - Given an authorized user with AUDIT_VIEW permission, When filtering by item, date range, event_type, or actor, Then results return within 2 seconds for up to 10,000 records and are exportable to CSV and JSON.
Upstream Changes While ETA Is Locked
- Given an item is locked by a manual override, When Jira/Linear target dates change, Then the displayed ETA remains the manual range and an "Upstream Drift" badge shows the delta in days between computed and manual ranges. - Then an audit record event_type=upstream_change is appended with new_computed_range and delta_days; customer-facing ETA remains unchanged. - When the lock expires or is manually released, Then the system recomputes the ETA and updates displays within 2 minutes, logging event_type=lock_released.
RBAC Enforcement for Overrides, Approvals, and Audit Access
- Given workspace roles [Owner, Admin, PM, Viewer] with permissions [ETA_OVERRIDE, ETA_APPROVER, AUDIT_VIEW], When a user without the required permission attempts an action, Then the system returns HTTP 403, shows an inline error, and logs event_type=permission_denied in the audit trail. - Given a PM with ETA_OVERRIDE but without ETA_APPROVER, When submitting a high-deviation override, Then the request is created as Pending Approval and cannot be self-approved. - Given a Viewer role, When viewing an item, Then they see the final ETA but not override controls or the full audit log.
Review Reminders and Auto-Revert on Expiry
- Given a locked override with an expiration date, When T-48h is reached, Then the assigned owner receives in-app and email notifications with actions [Renew, Edit, Revert]. - Given no action by the expiration time, Then the system auto-reverts to the computed ETA, appends audit event event_type=auto_revert, and notifies watchers. - Given workspace notification preferences, When email notifications are disabled, Then only in-app notifications are sent; notification delivery outcomes (success/failure) are logged.
ETA Data Model & Persistence
"As a developer, I want structured ETA fields exposed via API so that I can display accurate timelines in our internal dashboards and workflows."
Description

Extend the EchoLens data model to store upstream links and computed outputs: source_system, issue_ids, upstream_status, target_window_start/end, computed_eta_start/end, confidence, override flags, last_synced_at, last_computed_at, and change reasons. Index for fast retrieval and expose fields via API/SDK for external surfaces (e.g., Slack, dashboards). Ensures Live ETA Sync is durable, queryable, and easily integrated across the product.

Acceptance Criteria
Persist Upstream Linkage and Status
Given a sync receives an issue from Jira or Linear, When the record is persisted, Then source_system ∈ {jira, linear}, issue_ids is a non-empty array of strings, upstream_status is a non-empty string, and last_synced_at is set in UTC ISO-8601. Given the same upstream payload is processed again, When persistence occurs, Then no duplicate record is created and only changed fields are updated; last_synced_at is refreshed. Given an upstream issue key/ID changes, When a mapping update arrives, Then issue_ids includes the new ID while retaining prior IDs and the record remains uniquely addressable by source_system + canonical ID.
Compute and Store Range-based ETA and Confidence
Given target_window_start and target_window_end are present, When compute runs, Then computed_eta_start and computed_eta_end are stored as UTC ISO-8601 with computed_eta_start ≤ computed_eta_end and last_computed_at is updated. Given inputs (e.g., upstream_status or target window) change, When compute runs, Then computed_eta_* and confidence are recalculated and persisted only if values differ; last_computed_at reflects the time of change. Given confidence is produced, When stored, Then confidence is a numeric value between 0.0 and 1.0 inclusive with max 2 decimal places; null is allowed only when no computation can be made.
Override Flags Precedence Over Computed Values
Given override flags and override ETA values are set, When the record is read via API/SDK, Then override values take precedence over computed_eta_* for consumer-facing fields while computed values remain persisted. Given an override is cleared, When subsequent reads occur, Then the consumer-facing fields fall back to computed_eta_* values. Given an override is applied or cleared, When the change is saved, Then change reasons include an entry with code ∈ {override_on, override_off}.
Change Reasons Recorded on Data Mutations
Given any persisted field among {upstream_status, target_window_start, target_window_end, computed_eta_start, computed_eta_end, confidence, override flags} changes, When the update completes, Then a change reason entry is appended capturing reason_code (from a predefined set), human_readable summary (≤256 chars), and timestamp (UTC ISO-8601). Given change reasons exist for a record, When fetched via API/SDK, Then they are returned in reverse chronological order and are immutable (append-only).
Indexed Retrieval Performance and Coverage
Given indexes are in place, When querying by (source_system + any issue_id), Then the record is found with P95 latency ≤150 ms over a dataset of 100k records. Given a query filters by upstream_status or by a computed_eta_start/end date range or by last_synced_at range, When executed, Then P95 latency ≤200 ms and correct result sets are returned. Given a query sorts by last_computed_at desc, When executed with limit 50, Then order is correct and P95 latency ≤200 ms.
API/SDK Field Exposure and Validation
Given a GET request to the data model endpoint, When requesting a record, Then the response includes source_system, issue_ids, upstream_status, target_window_start, target_window_end, computed_eta_start, computed_eta_end, confidence, override flags, last_synced_at, last_computed_at, and change reasons with correct types. Given filter params {source_system, issue_id, upstream_status, computed_eta range, last_synced_at range}, When supplied, Then the API validates types and returns 400 with error details on invalid parameters or 200 with filtered results on valid parameters. Given the SDK is used, When compiling with type checking, Then all exposed fields are strongly typed and match API schema.
Idempotent Sync and Concurrency Safety
Given two identical sync payloads arrive concurrently, When persistence completes, Then exactly one logical update is applied (no duplicates) and last_synced_at reflects the latest completion time. Given two different payloads arrive out of order, When updates apply, Then optimistic locking or equivalent prevents lost updates and the final state reflects the newest upstream revision with monotonic last_synced_at. Given a transient failure occurs after write but before ack, When the payload is retried, Then the operation is idempotent and results in no unintended additional changes.

Smart Fallback

Delivers replies on the customer’s most responsive channel (Intercom, Zendesk, or email) with read-detection and automatic fallback if unopened. Increases visibility and acknowledgment rates so fixes don’t go unnoticed.

Requirements

Intercom/Zendesk/Email Connectors
"As a workspace admin, I want to securely connect our Intercom, Zendesk, and email providers so that Smart Fallback can deliver and track messages across all channels."
Description

Implement robust, secure integrations with Intercom, Zendesk, and email (SMTP or provider APIs like SendGrid) to enable cross-channel delivery and event ingestion. Support OAuth with least-privilege scopes, per-workspace credential storage, and health checks. Map EchoLens contacts to channel-specific identities (e.g., Intercom user_id, Zendesk requester, email address) with conflict resolution and fallback identifiers. Ingest webhooks/callbacks for delivered, opened/read, replied, bounced, and unsubscribed events. Maintain channel-specific threading (reply-in-thread for Intercom/Zendesk, proper email headers for threading), handle rate limits and retries with exponential backoff, and surface integration status in admin settings.

Acceptance Criteria
OAuth Connection and Least-Privilege Scopes
Given an admin initiates OAuth for Intercom, Zendesk, or the email provider, When redirected to the provider consent page, Then the requested scopes match the configured minimal scope list for that provider and no additional scopes are requested. Given OAuth completes successfully, When tokens are received, Then access/refresh tokens are stored per-workspace in encrypted storage and are never rendered in plaintext in UI or logs. Given tokens are expired or invalid, When the scheduled health check runs, Then the connector status is set to Disconnected with a specific error code/message within 60 seconds. Given a valid refresh token is present, When the access token expires, Then token refresh occurs automatically and subsequent API calls succeed without user action.
Workspace Credential Isolation and Health Checks
Given two workspaces A and B both connected to Intercom, When workspace A disconnects Intercom, Then workspace B remains connected and unaffected. Given an admin clicks Test connection for any connector, When the check runs, Then a live API call completes and the UI returns Healthy within 2 seconds if credentials and webhooks are valid; otherwise it returns Degraded or Disconnected with the primary failure reason. Given a transient network outage to a provider, When health checks run, Then status is set to Degraded and the system auto-retries every 5 minutes until a successful check restores status to Healthy.
Contact Identity Mapping and Conflict Resolution
Given a contact in EchoLens with email and known provider IDs, When identity mapping runs, Then the record stores intercom_user_id, zendesk_requester_id, and email as canonical identifiers. Given a contact is missing a provider-specific ID but has an email, When sending to that provider, Then the system uses email as the fallback identifier. Given multiple provider identities are discovered for the same email, When a conflict is detected (different provider IDs), Then the system prioritizes exact provider ID matches; if ambiguity remains, a conflict record is created, surfaced for resolution, and email is used for delivery until resolved. Given multiple provider records match the same email, When choosing a target identity, Then the most recent-activity record is selected; otherwise the contact is flagged for manual review and delivery falls back to email.
Event Ingestion for Delivered/Open/Reply/Bounce/Unsubscribe
Given a provider sends a webhook event, When the event arrives, Then the payload signature is verified and invalid signatures are rejected with 401 without side effects. Given duplicate webhook deliveries occur, When events share the same idempotency key, Then only one event is persisted and processed. Given a valid delivered/opened/read/replied/bounced/unsubscribed event is received, When processing completes, Then the corresponding message state is updated in EchoLens within 5 seconds and is visible in the timeline. Given a transient processing failure occurs, When retries are scheduled, Then the system retries with exponential backoff up to 5 attempts before placing the event in a dead-letter queue with error context.
Channel-Specific Threading Behavior
Given an existing Intercom conversation id is known, When EchoLens posts a reply, Then the message appears in the same conversation thread (not a new conversation). Given an existing Zendesk ticket id is known, When EchoLens posts a comment, Then the comment is added to the same ticket (no new ticket created). Given an existing email thread exists, When EchoLens sends a reply by email, Then the Message-ID and In-Reply-To/References headers are set such that the message threads correctly in Gmail and Outlook. Given no prior thread exists for a recipient, When EchoLens sends the first message, Then a new conversation/ticket/email thread is created and subsequent replies from EchoLens remain within that thread.
Rate Limits and Retry with Exponential Backoff
Given a provider returns HTTP 429, When retrying, Then the system respects Retry-After if present; otherwise it uses exponential backoff starting at 1s and doubling up to 32s with ±20% jitter, for a maximum of 5 retries. Given a provider returns HTTP 5xx, When retrying, Then the same backoff policy is applied and after the final attempt the message is marked Deferred with the surfaced error reason. Given multiple messages are retrying for a workspace and provider, When scheduling retries, Then no more than 2 concurrent retry workers run per workspace per provider to avoid exceeding rate limits. Given a retry eventually succeeds, When the provider acknowledges the request, Then the original message status is updated to Sent/Delivered and prior error states are cleared.
Integration Status in Admin Settings
Given an admin opens Settings > Integrations, When the page loads, Then each connector displays status (Connected/Degraded/Disconnected), last successful API call timestamp, last webhook received timestamp, and current pending retry count. Given the admin clicks Run health check for a connector, When the check completes, Then the result is shown within 5 seconds and an audit log entry is recorded with actor, timestamp, and outcome. Given credentials are revoked at the provider, When the next API call or health check occurs, Then the connector status updates to Disconnected within 10 minutes at most and a notification is sent to workspace owners.
Channel Responsiveness Scoring
"As a CX lead, I want Smart Fallback to choose the channel a customer is most likely to see so that our updates get noticed quickly."
Description

Compute a per-recipient, per-channel responsiveness score that predicts likelihood of open/acknowledgment within a defined SLA window. Use historical engagement signals (opens, replies, time-to-open), recency/decay, time-of-day/day-of-week affinity, segment attributes (plan, region), and channel availability to produce a confidence score with explanation. Provide cold-start defaults and continuous learning updates after each send. Expose scores via a service used by the orchestrator to select the initial channel and rank fallbacks, with guardrails to avoid overfitting and per-tenant isolation of data.

Acceptance Criteria
Initial Channel Selection Returns Ranked Scores
Given tenant_id T and recipient R with historical engagement and channels [Intercom, Zendesk, Email] and an SLA window W and send_time S When the orchestrator requests responsiveness scores Then the service returns for each channel: score in [0,1], available flag, and explanation with ≥3 feature contributions and their weights And the response includes a ranking sorted by score and a recommended_channel equal to the highest-scoring available channel And the response includes model_version and score_timestamp (UTC) And p95 response latency ≤ 200 ms
Cold Start Scoring With Segment-Aware Defaults
Given tenant_id T and recipient R with no historical engagement for any channel and segment attributes present (plan, region) and all channels available When the orchestrator requests responsiveness scores Then non-null scores in [0,1] are returned for all available channels And explanation.cold_start = true and explanation.prior_source ∈ {tenant_prior, global_prior} And segment attributes present in the request are listed in explanation.features And no data from other tenants is referenced in the explanation
Per-Tenant Isolation and Overfitting Guardrails
Given two tenants A and B with distinct data and identical recipient identifiers When computing scores for tenant A Then no features or events from tenant B influence the scores or appear in explanations And if tenant A’s per-channel training sample size < 500 events or unique_recipients < 100 (configurable) Then the service uses a regularized baseline model, sets explanation.guardrail_applied = true with reason = "insufficient_samples", and returns scores in [0,1]
Temporal Affinity and Recency Decay Are Reflected
Given recipient R with higher historical engagement on Mondays 09:00–11:00 local time and lower engagement outside that window When requesting scores with send_time within the high-affinity window versus outside it (holding other inputs constant) Then the within-window score for each available channel is ≥10% higher than the outside-window score And events older than 90 days contribute ≤50% of the weight of events from the last 7 days in the explanation.feature_weights
Channel Availability and Eligibility Affect Scores
Given a channel is unavailable for recipient R (e.g., missing user_id, unsubscribed, or disabled) When requesting scores Then available = false and either score = 0 or score = null with explanation.reason = "unavailable", and the channel is excluded from recommendation and ranking And if read detection is disabled for a channel, explanation includes read_detection = "disabled" and a score in [0,1] is still returned
Post-Send Outcome Ingestion and Online Learning
Given a send event at t0 on channel C to recipient R and an outcome event (opened/ack) at t1 When the outcome is ingested Then the feature store reflects the event within 5 minutes of t1 And the next score for R on channel C increases by ≥2% if opened/ack within SLA, or decreases by ≥2% if not opened/ack within SLA (subject to score bounds [0,1]) And duplicate outcome events with the same event_id are ignored (idempotent) And explanation.recent_events includes the last outcome with timestamp and outcome_type
API Contract, Validation, and Observability
Given a scoring request missing tenant_id or SLA window When the request is sent Then the service returns HTTP 400 with error_code and validation messages; unauthorized tenant_id returns 403; unknown channel returns 422 And for valid requests the response conforms to schema v1 and includes request_id, model_version, score_timestamp, ranking, and recommendation And p95 latency ≤ 200 ms and all requests emit metrics (request_count, latency, error_rate) without PII in logs
Unified Read Detection & Open Tracking
"As a product manager, I want reliable read tracking across all channels so that fallbacks only occur when a message truly wasn’t seen."
Description

Normalize delivery, open/read, and reply detection across channels into a single event model. For Intercom/Zendesk, rely on native read and reply indicators when available; for email, use signed open pixels with link-click tracking and heuristic fallbacks (e.g., reply detection via inbound parsing) while respecting privacy settings and honoring Do Not Track/consent. De-duplicate events across channels using message correlation IDs. Provide configurable SLAs for what constitutes ‘unopened’ to trigger fallbacks, and expose real-time state to cancel pending fallbacks when an open or reply is detected.

Acceptance Criteria
Normalize channel signals into unified events
Given a delivery, open/read, or reply signal is received from Intercom, Zendesk, or Email When the event ingestion pipeline processes the signal Then a single normalized event is stored with required fields: tenant_id, correlation_id, channel, message_id, event_type, event_time (UTC), source And the normalized event is retrievable from the events API and UI within the configured ingestion SLA And event_type mapping follows: delivery->delivered, native read->read, pixel open->opened, link click->opened, inbound/public reply->replied
Ingest native Intercom/Zendesk read and reply indicators
Given Intercom or Zendesk emits a native read indicator for a customer message When the webhook is received Then a 'read' normalized event is created for the matching correlation_id within the configured ingestion SLA And internal/agent-only actions are ignored Given a customer public reply in Intercom or Zendesk When captured via webhook or API poll Then a 'replied' normalized event is created for the matching correlation_id within the configured ingestion SLA
Email open and reply detection with signed pixel and link tracking
Given an outbound email includes a signed open pixel and tracked links When the recipient loads the pixel and the signature validates Then an 'opened' normalized event is created with the matching correlation_id And when the recipient clicks a tracked link Then an 'opened' normalized event is created if one does not already exist And when an inbound email reply is parsed with matching headers Then a 'replied' normalized event is created with the matching correlation_id And email client privacy prefetches are detected and flagged and do not cancel fallbacks unless followed by a click or a reply
Honor privacy, consent, and Do Not Track
Given Do Not Track is enabled or tracking consent is not present for the recipient or tenant When preparing an outbound email Then no tracking pixel or tracked links are added And no open or click events are stored for that message And reply detection via inbound parsing remains enabled And fallback decisions consider only allowed signals
De-duplicate events using correlation IDs across channels
Given multiple signals for the same logical message share a correlation_id across channels or retries When the signals are processed Then at most one normalized event per event_type is stored per correlation_id And subsequent duplicate signals are ignored without increasing counts And the events API returns a single current state per correlation_id
Configurable 'unopened' SLA to trigger fallback
Given tenant-configured 'unopened' SLAs per channel are set When a message has no read, open, or reply event within its channel SLA Then a fallback is scheduled on the next ranked channel And if a read or reply event is recorded before the scheduled send time Then the pending fallback is cancelled And SLA values are overrideable per message
Real-time state updates cancel pending fallbacks
Given a pending fallback exists for a correlation_id When an open, read, or reply event is recorded for that correlation_id Then the pending fallback job is cancelled within the configured cancellation SLA And the message state is updated to Opened or Replied in the UI and Events API in real time And a webhook is emitted indicating the state change
Automated Fallback Orchestrator
"As a CX lead, I want Smart Fallback to automatically resend via the next-best channel if a message isn’t opened so that customers still receive critical updates."
Description

Create a rules-driven service that selects an initial channel using responsiveness scores, schedules sends, and automatically triggers fallbacks when messages remain unopened past SLA thresholds. Support ordered channel lists, per-attempt delays, idempotent send operations, and cancellation on open/reply. Ensure thread continuity, message deduplication, and content consistency across channels. Enforce anti-spam safeguards (max attempts, cooldowns) and escalate to a final channel or human review after repeated failures. Persist full state and transitions for resilience and recovery, with observability (metrics, logs) for operations.

Acceptance Criteria
Initial Channel Selection Based on Responsiveness Scores
Given a contact has responsiveness scores per channel and an ordered channel list is configured When the orchestrator evaluates an outbound message Then it selects the channel with the highest responsiveness score; if scores tie, choose the channel appearing earliest in the ordered list And the selection is deterministic for identical inputs And the selected channel is verified as enabled for the account And the decision (channel, score, rationale) is persisted and auditable And if no scores exist, the first channel in the ordered list is selected
SLA-Based Fallback Trigger After Unopened
Given an initial send occurred at t0 with read-detection enabled and an SLA threshold X hours configured for attempt 1 When the message remains unopened and no reply is recorded until t0 + X Then the orchestrator schedules and sends the next attempt via the next channel in the ordered list And the configured per-attempt delay is honored before sending And only one fallback is triggered per SLA window And if the message is opened or replied before the scheduled send time, the fallback is not sent And the fallback decision (attempt index, timestamps, channel) is persisted
Idempotent Sends and Message Deduplication
Given each attempt has an idempotency key derived from [contact_id, thread_id, attempt_index, channel] When retries or concurrent workers execute the same attempt Then at most one provider API send occurs per unique idempotency key And duplicate provider callbacks are consolidated into a single state transition And repeated scheduling for the same attempt does not produce additional messages And deduplicated events are counted and logged
Automatic Cancellation on Open or Reply
Given pending scheduled attempts exist for a thread When the customer opens any prior message or replies on any channel Then all pending attempts for that thread are canceled within 60 seconds And no further messages are sent for that thread And the cancellation reason and triggering event are persisted in the audit log And a cancellation metric is emitted with the attempt indexes canceled
Thread Continuity and Content Consistency Across Channels
Given a fallback occurs from one channel to another When the message is sent on the new channel Then the subject/body variables resolve identically to the previous attempt And attachments and links are preserved or mapped to channel-compatible equivalents And the new message references the same external thread/conversation when the provider supports threading And a visible thread reference or reply quote is included where threading is unavailable And a content checksum matches across attempts after channel-specific formatting normalization
Anti-Spam Limits, Cooldowns, and Escalation on Repeated Failures
Given anti-spam settings are configured: max_attempts = N, per-contact daily_cap = D, cooldown = C When sends would exceed any of these limits Then the orchestrator suppresses the send and marks the attempt as skipped with reason And no more than N attempts are executed for a thread And no sends occur for the contact during cooldown C after the last attempt And hard bounces or spam complaints immediately halt the sequence and place the contact on a blocklist And after the final failed/unopened attempt, the orchestrator escalates by creating a task for human review or sending via the configured final channel And escalation details are persisted and exposed via API/UI
State Persistence, Recovery, and Observability
Given a long-running sequence with multiple attempts and a process restart or crash When the orchestrator recovers Then all in-flight threads resume from the last persisted state without duplicate sends And all state transitions (Pending, Scheduled, Sent, Opened, Replied, Failed, Skipped, Canceled, Escalated) are stored with timestamps and actor/source And metrics are emitted: sends_total, opens_total, replies_total, fallbacks_total, cancellations_total, attempts_in_progress, failures_total, escalations_total, deduplications_total, end_to_end_latency_ms And structured logs include correlation_id, idempotency_key, thread_id, contact_id, attempt_index, channel, and outcome And alert thresholds are configurable for failure rate, open rate below target, and duplicate suppression anomalies
Policy & SLA Configuration
"As an admin, I want to define fallback rules, quiet hours, and limits so that outreach is effective, compliant, and non-intrusive."
Description

Provide an admin UI and API to configure channel priorities, SLA timeouts, max attempts, quiet hours by recipient timezone, and per-segment policies (e.g., VIPs, regions). Include frequency caps and global suppression lists, honoring unsubscribes/opt-outs and consent requirements. Allow workspace- and project-level overrides, previews of policy outcomes, and validation to prevent conflicting rules. Store versioned policies with effective dates and audit history, and surface a dry-run mode to show what would be sent without actually dispatching.

Acceptance Criteria
Save Policy with Channel Priorities, SLA Timeouts, and Max Attempts via Admin UI/API
Given I am an admin in workspace W When I create or update a policy specifying channel_priorities=[Intercom, Zendesk, Email], sla_timeouts={Intercom:4h, Zendesk:8h, Email:12h}, max_attempts={Intercom:2, Zendesk:2, Email:3} Then the policy is persisted with a new version number and policy_id And GET /policies/{policy_id} returns the same values and version And values are validated: priorities contain only supported channels and no duplicates; timeouts are 1m–72h; attempts are 0–10 integers And invalid inputs reject with HTTP 422 and field-level errors; no new version is created
Enforce Quiet Hours by Recipient Timezone
Given a recipient with timezone America/Los_Angeles and policy quiet_hours=21:00–07:00 When a send is evaluated at 22:15 local time Then delivery is deferred until 07:00 local time and the scheduled_time reflects the local timezone And if fallback SLA would elapse during quiet hours, the next attempt is scheduled at 07:00 unless the policy allows VIP override And when timezone is unknown, the workspace default timezone is used
Per-Segment Policies and Workspace/Project Overrides Resolution
Given a workspace default policy P0 and a project-level override P1 for Project A And segment rules for VIP and Region=EU exist in P1 with conflicting quiet_hours When resolving an effective policy for recipient R (VIP=true, Region=EU) in Project A Then the resolution applies project override P1 over P0 and uses the most-specific segment precedence VIP > Region > All And GET /policies/effective?project=A&recipient=R returns the resolved values with a trace of contributing rules And removing P1 reverts the effective policy for R to P0 immediately
Frequency Caps, Global Suppression, and Consent Enforcement
Given a global frequency cap of 2 messages per rolling 24h per recipient and a global suppression list containing user@example.com And recipient R has opted out of Email and lacks consent for marketing in Region=EU When evaluating a send or dry-run for R Then no dispatch occurs to suppressed recipients or opted-out channels and the outcome includes reason in [global_suppression, opt_out, consent_missing, frequency_cap] And when R has already received 2 messages in the last 24h, the result is suppressed with reason=frequency_cap And the audit log records the suppression reason and policy version used
Policy Validation Prevents Conflicting or Overlapping Rules
Given two rules target the same segment with different channel_priorities and overlapping effective_date ranges When attempting to save the policy Then the save is blocked with HTTP 422 and an error payload listing conflict codes and the conflicting rule IDs And the UI highlights the fields and prevents activation until conflicts are resolved And non-overlapping effective_date windows for the same segment are accepted
Policy Versioning with Effective Dates and Audit History
Given current policy version v3 is active When an admin saves version v4 with effective_date=2025-09-01T00:00:00Z Then v4 is stored as scheduled and becomes the active policy at the effective_date without downtime And GET /policies/current?at=2025-08-20 returns v3 while at=2025-09-01 returns v4 And the audit log records actor, timestamp, IP, and a diff of changed fields And an admin can rollback to v3, creating v5 with effective_date=immediate and an audit reason
Policy Outcome Preview and Dry-Run Mode
Given a candidate message to recipient R under project A When I run Preview in the UI or call POST /policies/dry-run with recipient, project, and message metadata Then the response shows: selected initial channel, fallback chain per SLA and max_attempts, scheduled times accounting for quiet_hours, and any suppression/consent outcomes And dry-run returns would_send=true|false, selected_channel (or null), scheduled_time (or null), and reasons[] And dry-run creates no outbound provider calls or tasks and does not affect frequency cap counters
Message Templates & Personalization
"As a product marketer, I want reusable templates that render correctly on each channel so that our updates are clear and on-brand."
Description

Enable channel-aware templates for fix and update notifications with variables for issue/theme, ticket links, changelog entries, and recipient name. Support localization, brand styling, safe markdown/HTML rendering, and channel-specific constraints (e.g., Intercom blocks, Zendesk public comment formatting, email MIME). Provide test sends, previews per channel, and link tracking parameters. Ensure content parity across fallback attempts while allowing minor channel-specific adjustments, and guard against PII leakage by applying role-based redaction rules.

Acceptance Criteria
Channel-Aware Template Selection & Validation
Given a notification must be sent via Intercom, Zendesk, or Email When the system prepares the message Then it selects the corresponding channel template variant for that channel And validates content against channel-specific constraints (Intercom blocks, Zendesk public comment formatting, email MIME structure) And blocks the send and surfaces a descriptive validation error if any constraint is violated And logs the selected template ID, channel, and validation outcome for audit
Variable Substitution and Safe Defaults
Given a template includes variables for issue/theme, ticket_link, changelog_entry, and recipient_name When a preview or send is generated Then all variables are substituted with the provided values And if a value is missing, a safe default or omission rule is applied without rendering raw placeholders And links render as clickable, channel-appropriate entities And a substitution report lists variables used, missing, and defaulted
Localization and Brand Styling Application
Given a recipient has a locale and a brand profile is configured When generating previews or sends Then copy is localized using the matching locale resource And falls back to the default locale if missing while flagging the fallback in logs And brand styling (logo, colors, typography) is applied within channel capabilities And RTL/LTR text direction is respected per locale
Safe Markdown/HTML Rendering and PII Redaction by Role
Given content includes markdown/HTML and potential PII When rendering for each channel Then only the allowed tag/attribute whitelist is preserved and all others sanitized And links are secured with appropriate attributes where applicable (e.g., rel="noopener noreferrer") And role-based redaction rules mask PII fields (emails, phone numbers, account IDs) for unauthorized users in previews and sends And an audit log records which fields were redacted and the governing rule
Per-Channel Preview and Test Send with Tracking
Given a user requests a preview or test send for a specific channel When the preview/test send is generated Then the visual and structural output matches the final send rendering engine for that channel And all external links include the configured tracking parameters (e.g., utm_source=echolens, channel, campaign) And the test send is delivered to a designated sandbox/inbox without contacting end customers And a delivery receipt or error status is returned within 10 seconds
Content Parity Across Smart Fallback Attempts
Given Smart Fallback triggers a resend to a different channel due to unread status within the configured window When the fallback message is prepared Then the core content (subject/title, body text, primary links, changelog entry) remains semantically identical to the original send And only channel-specific adjustments are applied (e.g., Intercom blocks vs. email HTML vs. Zendesk formatting) And a parity check compares tokenized content and reports a similarity score of ≥ 0.95 And message threads link the original and fallback attempts for traceability
Delivery Analytics & Audit Trail
"As a PM, I want visibility into which channels drive acknowledgments so that we can optimize our fallback rules and content."
Description

Build dashboards and exports showing send volume, open/ack rates, time-to-open, fallback rates, and performance by channel, segment, and time window. Provide drill-down per recipient/message timelines, correlation with theme severity, and cohort comparisons before/after Smart Fallback. Support CSV export and API access, plus alerts for anomalies (e.g., spikes in bounces). Maintain a tamper-evident audit log of policies applied, attempts made, events received, and user overrides for compliance and postmortems.

Acceptance Criteria
KPI Dashboard by Channel, Segment, and Time Window
Given event data exists for the selected time window When a user applies channel, segment, and time window filters in the dashboard Then the dashboard displays send_volume, open_rate, acknowledgment_rate, median_time_to_open_ms, and fallback_rate matching raw event store calculations within 1% Given no events match the applied filters When the dashboard loads Then KPIs display zeros with a "No data for selection" state and no client or server errors are logged Given a dataset of up to 100k events within the selected window When filters are applied Then KPI tiles render in ≤2000 ms at the 95th percentile Given identical filters are applied via API and UI When KPI values are compared Then values are identical up to 2 decimal places
Recipient-Level Drill-Down and Message Timeline
Given a user clicks a KPI value or data point When the drill-down panel opens Then it lists per recipient a chronological timeline of attempts with ISO 8601 timestamps (timezone-aware), selected channel(s), read/open events, fallbacks, and final acknowledgment status Given a message timeline item is expanded When details are shown Then attempt_id, policy_id/version, routing decision, delivery provider response code, and latency to open/ack are present Given 10k or more records in drill-down When paginating or searching by recipient email/ID Then results return within ≤1500 ms at P95 and remain sorted by time descending Given PII masking rules are enabled When drill-down renders Then masked fields display according to policy and raw values are not retrievable via UI network responses
Severity Correlation Analytics
Given events labeled with theme severity (Low/Medium/High/Critical) When the user enables "Correlate severity" Then the UI displays correlation between severity and open_rate, acknowledgment_rate, and median_time_to_open_ms with confidence intervals Given a reference validation dataset When correlation metrics are computed Then Pearson r and group means match the reference job within ±0.02 and ±1% respectively Given severity labels are missing for more than 5% of events When correlation is computed Then the UI indicates coverage, excludes unlabeled events by default with an option to include them, and metrics update accordingly
Pre/Post Smart Fallback Cohort Comparison
Given cohorts segmented by period before and after Smart Fallback enablement When a user selects a segment and time window Then the comparison view displays deltas for open_rate, acknowledgment_rate, fallback_rate, and median_time_to_open_ms with 95% confidence intervals and sample sizes Given any cohort metric has fewer than 500 messages When the comparison is requested Then the UI displays "insufficient sample" for confidence intervals and hides the delta arrow while still showing raw rates Given identical filters are applied When the comparison CSV is downloaded Then the numbers match the on-screen values to 2 decimal places
CSV Export and Analytics API Access
Given any current filter set in the UI When the user exports CSV Then the file contains one row per aggregation bucket with columns: time_window_start, time_window_end, channel, segment, send_volume, open_rate, acknowledgment_rate, median_time_to_open_ms, fallback_rate, bounce_rate, and values match the UI KPIs to 2 decimal places Given an API client with a valid token When it requests GET /v1/analytics with equivalent filters Then the API returns JSON with paginated results and total_count that matches the CSV and UI values; requests with invalid/expired tokens return 401 Given the export size exceeds 50 MB When the user requests CSV Then generation runs asynchronously, the user is notified, and a signed URL valid for 24 hours is provided upon completion
Anomaly Alerts for Bounce Spikes
Given a rolling 7-day baseline per channel and segment When the last 60-minute bounce_rate exceeds the baseline by more than 3 standard deviations and at least 2 percentage points with N ≥ 200 messages Then an alert is triggered within 2 minutes Given an alert is triggered When notifications are dispatched Then Slack, email, and webhook payloads include channel, segment, time window, baseline, observed rate, z_score, and a deep link to the dashboard Given the anomaly condition persists When subsequent evaluations run Then alerts are de-duplicated for 60 minutes per channel/segment to prevent alert storms
Tamper-Evident Audit Log and Verification
Given any delivery attempt occurs When policies are applied and events are received Then an audit log entry is appended with monotonic sequence number, timestamp, actor (system/user), policy_id/version, attempt_id, channel decisions, provider responses, user overrides, and a cryptographic hash linking to the previous entry (hash chain) Given an audit log export is produced When a verifier runs VerifyChain(head_hash) Then the chain validates end-to-end with no gaps; any modification yields a verification failure and the index of the first invalid entry Given a user with the "Audit Viewer" role When querying the audit log by attempt_id or recipient Then results return within ≤2000 ms for up to 1,000,000 entries and sensitive payload fields are redacted per policy; users without the role receive 403

Reply Variants

Generates a few goal-optimized drafts (concise, detailed, or executive-ready) with inline rationale. Teams pick or A/B a variant, learn what resonates, and standardize winning language to boost engagement over time.

Requirements

Multi-Variant Draft Generation
"As a CX agent, I want the tool to generate multiple goal-specific reply options from the customer’s message so that I can choose the best response quickly without starting from scratch."
Description

Generate 3–5 reply drafts per conversation, optimized for goals (concise, detailed, executive-ready) and channel (email, chat, ticket). Ingest the original customer message, EchoLens theme cluster context, sentiment, account tier, and recent product changes to ground responses. Each draft includes inline rationale markers referencing the evidence and relevant policies. Enforce p95 generation latency under 3 seconds and support automatic language detection with locale-aware formatting. Apply safety filters to avoid PII leakage, speculative commitments, and off-brand phrasing. Expose a simple API consumed by the composer across all supported integrations.

Acceptance Criteria
Generate 3–5 Goal- and Channel-Optimized Variants
- Given a request with channel ∈ {email, chat, ticket} and optional goals ⊆ {concise, detailed, executive_ready}, when variants are generated, then return between 3 and 5 variants. - If goals is omitted, include at least one of each: concise, detailed, executive_ready. - Each variant is labeled with its goal and channel and adheres to channel constraints: - chat: ≤ 500 characters; single-sentence greeting; no signature block. - email: 80–250 word body; includes greeting and sign-off. - ticket: uses formal tone and includes an enumerated next-steps list (≥ 2 items). - Variants must be substantively distinct with pairwise Jaccard similarity on sentences < 0.8. - The API returns total_variant_count and an array variants[3..5]; requests yielding <3 valid variants return HTTP 422 with reason.
Grounding With Conversation and Product Context
- Each variant references the customer issue by paraphrasing the core problem in ≤ 1 sentence without echoing PII. - Rationale markers in each variant include links to: theme_cluster_id, sentiment (label and score), account_tier, and any relevant product_change_id from the last 30 days; if none apply, include a [change:none] marker. - Tone and prioritization reflect account_tier: enterprise variants mention escalation/ownership; self-serve tiers emphasize help-center guidance. - If sentiment is negative, variants include an empathy acknowledgement sentence; if positive, retain appreciative tone. - An ablation test removing any single context source (theme/sentiment/tier/change) must change at least one variant with cosine similarity delta ≥ 0.10 compared to the full-context output.
Inline Rationale Markers and Evidence References
- Each paragraph contains ≥ 1 inline rationale marker formatted [type:id] where type ∈ {msg, theme, senti, policy, change} and id resolves to a source. - Response includes a rationale array mapping each marker to: type, source_id, source_excerpt (≤ 200 chars), and confidence ∈ [0,1]. - Any claims about refunds, timelines/ETAs, or SLAs include an explicit [policy:*] marker; responses lacking required policy markers are rejected with HTTP 422. - All markers resolve to provided context or policy store entries; no unresolved/dangling markers are allowed. - Marker positions are provided via character offsets (start,end) for highlighting in the composer.
p95 Latency Under 3 Seconds at Target Load
- Under 20 concurrent requests and typical payloads (0.5–2 KB), p95 end-to-end latency (request receipt to response send) for HTTP 200 responses is ≤ 3000 ms over a sample of ≥ 1000 requests. - p99 latency is ≤ 5000 ms under the same test; timeouts return HTTP 504 within 5500 ms and include a retry_after hint. - Measured latency includes all steps: context retrieval, generation, safety filtering, and rationale rendering. - Requests exceeding 3 seconds must be logged with latency_breakdown for analysis; no more than 1% of requests may bypass safety filtering due to timeouts (otherwise fail the criterion).
Automatic Language Detection and Locale Formatting
- The system detects the input language with confidence; if confidence ≥ 0.80, variants use the detected language; otherwise, default to request.locale. - Response includes detected_language and detection_confidence; all variants are returned in target locale. - Dates, numbers, and currency in the text follow locale conventions (e.g., en-US: mm/dd/yyyy and 1,234.56; de-DE: dd.mm.yyyy and 1.234,56). - For right-to-left locales, output uses correct bidi control and punctuation mirroring; no mixed-direction rendering issues in composer acceptance tests. - If channel = email, capitalization and punctuation follow locale norms in greeting and sign-off.
Safety Filters: PII, Speculative Commitments, Off-Brand Phrasing
- Variants must not expose PII (emails, phone numbers, postal addresses, tokens). Detected PII is masked as [redacted] and referenced only via rationale markers. - Variants avoid speculative commitments (specific dates/guarantees) unless a compliant [policy:*] marker authorizes the statement; otherwise, use conditional language. - Outputs contain no disallowed content per safety policy (e.g., profanity, sensitive attributes); violations return HTTP 422 with error_code = SAFETY_VIOLATION and safety_flags. - Each variant passes a brand voice check with score ≥ 0.75; on failure, one regeneration attempt is made; if still < 0.75, return HTTP 409 with safety_flags and zero variants delivered.
Composer-Consumable API Contract and Integrations
- POST /v1/reply-variants accepts JSON: message.text (string), channel (enum), goals[] (enum), theme_cluster_id (string), sentiment {label, score}, account_tier (enum), locale (IETF BCP 47), product_change_ids[] (string), request_id (string). Auth via Bearer token; optional Idempotency-Key header supported. - On success (HTTP 200), response includes: request_id, detected_language, total_variant_count, variants[] (3–5) with fields {id, goal, channel, language, text, rationale_markers[], safety_flags[], created_at, latency_ms}. For channel = email, include subject (string) in each variant. - Error handling: 400 (invalid input), 401 (auth), 422 (safety/policy violations or <3 valid variants), 429 (rate limited), 5xx (server). Errors include code and message. - Backward compatibility: new fields are additive; breaking changes require versioned path (e.g., /v2). - Integration tests demonstrate the composer across all supported integrations can render variants and highlight markers using the returned schema without client-side transformations.
Goal & Tone Controls
"As a product manager, I want to adjust the tone and goal of generated replies so that the message aligns with our brand and the recipient’s expectations."
Description

Provide a composer-side control panel for selecting reply goal, tone (empathetic, neutral, assertive), reading level, and length constraints. Selections parameterize the generation service and are persisted per user/team with admin-managed presets. Defaults auto-select based on channel, recipient seniority inferred from CRM, and sensitivity of the theme cluster. Changes render an instant preview without losing cursor position. All settings are audit-logged for governance.

Acceptance Criteria
Auto-Default Selection on Composer Open
Given a composer is opened for a conversation with known channel, recipient seniority from CRM, and theme sensitivity When no user-level or team preset overrides exist Then the goal, tone, reading level, and length controls are auto-selected according to the configured mapping rules for channel, seniority, and sensitivity And if any signal is missing, the system applies system defaults defined in configuration and records which signals were unavailable And the applied defaults are reflected immediately in the visible control values
Control Selections Parameterize Generation Service
Given a user has selected goal, tone, reading level, and length in the control panel When the user requests a preview or generation Then the request payload sent to the generation service includes the selected goal, tone, readingLevel, and lengthConstraint values And the returned variants respect the lengthConstraint (measured against configured bounds) and do not exceed the max limit And the returned variants meet or are easier than the selected reading level (Flesch–Kincaid Grade Level <= target) And the system logs the parameters used for the request for traceability
User and Team Persistence of Control Selections
Given a user adjusts any of the controls and completes a reply or explicitly saves their selection When the user opens the composer for a new conversation of the same channel within the same team context Then the last-used selections are restored for that user And precedence is applied as: user-saved selections > team preset default > auto-default mapping And a user can clear their saved selections, after which team preset or auto-defaults will apply on next open
Admin-Managed Presets Creation and Application
Given an admin with appropriate permissions When the admin creates or updates a team preset defining goal, tone, reading level, and length (optionally scoped by channel) Then the preset is stored and becomes the default for team members who have no user-level overrides And changes take effect for all applicable users on next composer open And when a preset is deleted, affected users fall back to their saved selections or auto-default mapping if none exist
Instant Preview Without Cursor Displacement
Given a user is typing in the composer at a specific caret position When the user changes any control (goal, tone, reading level, length) Then the preview refreshes without the composer losing focus And the caret position and selection range in the composer remain unchanged And the preview update completes within 500 ms at the 95th percentile across 1000 interactions under normal network conditions
Audit Logging of Control Changes
Given any change to goal, tone, reading level, or length selection occurs (including auto-default application and preset application) When the change is applied Then an audit log entry is written capturing timestamp (UTC), userId, teamId, conversationId (if applicable), control name, oldValue, newValue, source (user, auto-default, team-preset), and requestId And audit entries are immutable, queryable by admins, and available within 5 seconds of the change And audit logging continues to function during preview-only changes as well as final send
Preview Request Concurrency and Last-Change-Wins
Given a user rapidly toggles multiple controls When multiple preview requests are initiated in quick succession Then in-flight preview requests are canceled or superseded so that only the most recent control state is rendered And no stale preview is displayed after the final change And error states from canceled requests are not surfaced to the user
Inline Rationale Annotations
"As a CX lead, I want to see the rationale behind a draft so that I can verify accuracy before sending."
Description

Display toggleable, inline annotations on each variant that explain sentence-level choices (e.g., linking to known issues, roadmap status, or policy lines). Tooltips cite data sources with confidence scores from EchoLens clusters and include timestamps. Annotations are excluded from the text when inserted or sent. Provide quick actions to open the linked Jira/Linear item or the live theme map for deeper context. Improve trust and reduce review time without cluttering the composer.

Acceptance Criteria
Variant-level Annotation Toggle
Given a composer with multiple reply variants When the user toggles "Show annotations" on Variant X Then inline annotation markers appear only on Variant X within 300ms And other variants remain unaffected And the toggle defaults to Off for new composer sessions and newly generated variants And the toggle state for Variant X persists while the composer is open, including after switching variants or editing content
Sentence-level Rationale Content
Given annotations are shown on a variant When a sentence contains a reference to a known issue, roadmap item, or policy line Then an annotation marker is rendered at the end of that sentence with a category label (Issue/Roadmap/Policy) And the inline annotation includes a one-line rationale explaining the sentence choice And at most one marker is shown per sentence; additional rationale details are available via the tooltip And if no qualifying reference exists, no marker is shown for that sentence
Tooltip Data Sources, Confidence, and Timestamps
Given the user hovers or focuses an annotation marker When the tooltip is invoked Then the tooltip appears within 200ms and remains visible while hovered/focused And the tooltip lists the EchoLens cluster name/ID, data source type(s), a confidence score as a percentage with one decimal (e.g., 87.3%), and the source timestamp in ISO 8601 UTC And if multiple sources exist, the tooltip shows the top 3 by relevance plus a "+N more" indicator
Annotations Excluded from Inserted/Sent Text
Given annotations are visible in the composer When the user selects "Insert variant" or "Send" Then the resulting content contains only the variant text without any annotation markers, rationale text, or tooltip content And the delivered/inserted message matches the plain variant text exactly And QA validation confirms no annotation artifacts in the destination channel (email/chat/ticket)
Quick Actions to Jira/Linear and Theme Map
Given an annotation references a Jira or Linear item or an EchoLens theme cluster When the user clicks "Open in Jira/Linear" Then the linked item opens in a new browser tab within 1500ms, or an error toast appears with a Retry action if it fails And when the user clicks "Open Theme Map" Then the live theme map opens with the referenced cluster preselected within 1000ms And quick actions are disabled when no valid link is present
Non-cluttering UI and Performance
Given annotations are toggled On Then annotation markers do not push text onto a new line except at natural wraps And toggling annotations On/Off completes within 100ms for replies up to 5,000 characters And editor scroll performance remains at or above 50 FPS for replies up to 10,000 characters on a mid-tier laptop And if more than 40% of sentences would be annotated in a paragraph, remaining annotations collapse into a concise "+N annotations" badge to reduce visual clutter
Accessibility and Keyboard Navigation
Given a keyboard-only or screen-reader user is composing a reply When tabbing through a variant with annotations Then each annotation marker is reachable in tab order with a visible focus state And tooltips are operable via Enter/Space and dismissible via Escape, with role="tooltip" and aria-describedby referencing the annotated sentence And annotation icons and text meet a minimum 4.5:1 contrast ratio And quick actions in tooltips are operable via keyboard
A/B Variant Testing
"As a CX manager, I want to A/B test reply variants so that we learn which language increases engagement."
Description

Enable selection of two variants for randomized delivery within a defined cohort and time window. Track open rate, reply rate, link clicks, CSAT, and time-to-resolution via helpdesk/email integrations. Compute lift, significance, and confidence intervals, and flag a winner automatically. Attribute results to goals, tones, and themes to inform future generation and presets. Support guardrails to cap exposure of experimental variants on sensitive accounts.

Acceptance Criteria
Variant Selection and Test Configuration
- Given an authorized EchoLens user opens a conversation with generated reply variants and chooses A/B Test, When they configure exactly two variants, a named cohort segment, start date/time (UTC), end date/time (UTC) or duration, traffic split (default 50/50), and a primary metric, Then the Start Test action is enabled only after all required fields pass validation. - Given valid inputs, When Start Test is clicked, Then the system creates an immutable test ID, persists the configuration, and locks all parameters except end date (which can be extended or shortened before it elapses). - Given invalid or missing inputs, When the user attempts to start, Then field-level error messages are shown and the test is not created. - Given a running test, When retrieved via UI or API, Then the exact saved configuration values are returned without mutation.
Randomized Delivery within Defined Cohort and Window
- Given an active test and a defined cohort filter, When an outbound reply is sent to a recipient matching the cohort within the configured time window, Then the recipient is assigned to Variant A or B via stable hashing on recipient ID and test ID using the configured split. - Given a recipient already assigned in a thread, When subsequent replies are sent in the same thread, Then the same variant is consistently applied (no crossover). - Given a recipient outside the cohort or time window, When a reply is sent, Then no assignment occurs and the control variant is used. - Given N ≥ 1000 assigned recipients with a 50/50 split, Then observed allocation is within ±1.5% of target; for 100 ≤ N < 1000, allocation is within ±5% of target.
Metric Tracking and Attribution via Integrations
- Given Zendesk, Intercom, or Gmail integrations are connected, When replies are delivered, Then EchoLens ingests open, reply, link-click, CSAT, and resolution events with timestamps and thread/conversation IDs within 5 minutes of provider event at p95. - Given duplicated or delayed provider events, When processing, Then events are de-duplicated by provider event ID and late arrivals backfill aggregates without double-counting. - Given variant assignments exist, When metrics are stored, Then each event is attributed to test ID, variant ID, conversation ID, and recipient ID and is visible in UI drill-down and export/API. - Given a conversation is closed, When measuring time-to-resolution, Then TTR is computed as time from first EchoLens-assisted reply sent to first provider status of Solved/Closed and this definition is displayed in the UI.
Lift, Significance, and Confidence Intervals Calculation
- Given per-variant metric data, When calculating rates (open, reply, click) and means (CSAT 1–5, TTR in hours), Then EchoLens computes lift (difference A vs B), two-sided 95% confidence intervals, and p-values using two-proportion z-tests for rates and Welch’s t-test for means; bootstrap (10,000 resamples) is used when normality fails (Shapiro–Wilk p < 0.05) or n < 30. - Given a user-selected primary metric and minimum sample size requirement, When current samples are below the required level, Then results are labeled Underpowered and no winner is selected. - Given new events arrive, When recomputation runs, Then results update within 1 minute and previous calculation snapshots are retained with timestamps for auditability.
Automatic Winner Flagging and Test Stop
- Given a running test with a primary metric and alpha threshold (default 0.05; configurable 0.01–0.10), When a variant shows positive lift on the primary metric with p < alpha and all guardrails are satisfied, Then EchoLens automatically flags the winner and notifies test owners. - Given a flagged winner, When the user selects Roll Out 100%, Then routing is updated to deliver the winning variant to 100% of the cohort and an audit log records user, timestamp, previous split, and new split. - Given a guardrail failure (e.g., CSAT lift < -5% absolute or TTR increase > 10% with p < 0.10), When detected, Then the test is auto-paused, experimental variants stop receiving traffic, and a Guardrail Breach banner is displayed with details.
Guardrails and Exposure Caps for Sensitive Accounts
- Given a designated sensitive account list or tag and an exposure cap (absolute N or % of cohort) for experimental variants, When assignment occurs, Then cumulative exposure to non-control variants for sensitive accounts never exceeds the configured cap. - Given a manual send to a sensitive account during an active test, When a user attempts to select an experimental variant, Then a warning modal is shown and control is preselected by default unless the user has override permission and provides a justification; overrides are captured in the audit log. - Given the next assignment would breach the cap, When a send is attempted, Then assignment falls back to control and a capacity-reached event is logged.
Results Attribution to Goals, Tones, and Themes
- Given each variant is tagged with generation goal, tone, and EchoLens theme IDs, When computing results, Then performance is aggregated by these tags and displayed as top-performing combinations with lift and 95% CIs. - Given a results export via CSV or API, When requested, Then each row includes test ID, variant ID, goal, tone, theme IDs, metric values, lift, confidence intervals, and p-values. - Given presets management, When a tag or tag-combination outperforms across ≥3 tests with p < 0.05 on their respective primary metrics, Then EchoLens recommends updating generation presets and surfaces the recommendation in the presets UI.
Winning Language Library
"As an enablement lead, I want to standardize high-performing wording so that the team can respond consistently and improve over time."
Description

Provide a governed repository for saving winning variants as reusable snippets/templates with tags, audiences, languages, and channel-specific formatting. Include versioning, approval workflow, change history, and deprecation controls. Surface context-aware suggestions during drafting and auto-refresh templates based on A/B outcomes and performance trends. Enforce role-based permissions for create, publish, and edit actions.

Acceptance Criteria
Create and Tag Template from Winning Variant
Given a user with the Creator role selects a winning reply variant from recent A/B results When they click "Save as Template" and complete required metadata (title, tags, audience, language, channel formatting) Then the system creates a template record with version 1.0, author, created timestamp, and all provided metadata And required fields are validated; missing fields show inline errors and block save And channel-specific formatting preview renders correctly for the selected channel(s) And the template is searchable by title, tags, audience, and language within 1 second p95
Template Versioning and Change History
Given a published template at version 1.0 exists When an Editor updates content or metadata and submits the change Then a draft version 1.1 is created; version 1.0 remains the active published version until approval And change history shows a side-by-side diff of content and metadata with who and when fields And version numbers auto-increment for each submitted change; published versions are immutable And users can revert to any prior published version in one action
Approval Workflow and Publishing Governance
Given roles are configured as Creator, Editor, Approver, Viewer When a draft template is submitted for approval Then only users with the Approver role can approve and publish the draft And approval publishes the draft within 2 seconds p95 and updates status to Published And rejection requires a mandatory reason and keeps the draft in an Editable state And approvers and submitter receive notifications on approve/reject events
Template Deprecation and Sunset Controls
Given a template is in Published state When an Approver marks it as Deprecated and sets a sunset date (future date required) Then the template is excluded from suggestion surfaces immediately and flagged as Deprecated via API And any active drafts using the template display a deprecation banner with an optional suggested replacement And after the sunset date, the template cannot be inserted into new drafts And deprecation and sunset actions are recorded in the audit log
Context-Aware Suggestions in Composer
Given a user is drafting a reply with detected context (audience, language, channel, and theme tags) When the composer requests suggestions Then the system returns the top 3 templates ranked by contextual match and recent performance uplift And each suggestion displays a rationale (matching tags, audience/language/channel fit, performance metrics, confidence score) And deprecated or unauthorized templates are excluded And end-to-end suggestion latency is <= 500 ms p95
Auto-Refresh from A/B Outcomes
Given A/B test results exist for variants linked to a template family When a winner is detected with p-value <= 0.05, sample size >= 200 per arm in the last 30 days, and uplift >= 5% on the primary KPI Then the system generates an auto-refresh draft (minor version) with proposed content changes and attached metrics rationale And approvers are notified; if auto-approve is enabled for this template, the draft is auto-published, otherwise it awaits approval And all auto-refreshed publishes link back to the experiment ID and store pre/post performance snapshots And templates opted out of auto-refresh are not updated
Role-Based Permission Enforcement and Auditability
Given RBAC is configured with Admin, Creator, Editor, Approver, and Viewer roles When users attempt actions (create, edit, submit for approval, approve, publish, deprecate, delete, restore) Then the system enforces permissions per role and blocks unauthorized actions with a 403 and user-friendly message And every action writes an audit log entry including actor, timestamp, action, target template/version, and diff summary And audit logs are filterable by actor, action, and date range and exportable as CSV And permission changes take effect within 1 minute of role update
Helpdesk and Email Insert & Sync
"As an agent, I want to insert the chosen draft directly into my helpdesk composer so that I can send faster and keep context in one place."
Description

Offer one-click insertion of the selected variant into Zendesk, Intercom, Front, Gmail, and Outlook composers while preserving placeholders (customer name, ticket ID) and signatures. Normalize Markdown/HTML, quoting, and threading per channel. Sync back which variant and template were used to EchoLens for analytics and A/B attribution. Provide fallbacks when integrations are offline and ensure idempotent inserts to avoid duplicate content.

Acceptance Criteria
One-Click Insert Into Supported Composers
Given a user has selected a reply variant in EchoLens and an active composer is open in Zendesk, Intercom, Front, Gmail, or Outlook with a loaded conversation When the user clicks "Insert" for the matching channel Then the variant content is inserted at the cursor position in the active composer within 2 seconds And existing draft content outside the cursor location is left unchanged And the composer remains focused with the cursor placed after the inserted content And no additional confirmation dialog is required
Placeholder Preservation Across Channels
Given the selected variant contains placeholders in double-curly syntax (e.g., {{customer_name}}, {{ticket_id}}) When the variant is inserted into any supported composer Then all placeholders are inserted verbatim and unescaped And no placeholder is auto-resolved, removed, or HTML-escaped by EchoLens And the draft contains the exact placeholder tokens present in the variant
Signature Placement and Deduplication
Given the target composer has an existing signature block enabled When the variant is inserted Then the variant content appears immediately above the signature block And the signature block appears exactly once after insertion And subsequent inserts do not create duplicate signature blocks
Channel-Specific Formatting, Quoting, and Threading
Given the variant includes Markdown/HTML (headings, lists, links, emphasis) and inline quoting When the variant is inserted into Zendesk, Intercom, Front, Gmail, or Outlook Then formatting is normalized to render correctly in that channel's composer (Markdown converted to rich text where unsupported; unsupported HTML stripped while preserving text) And the inserted content is placed above quoted thread history in email composers and in the reply area (not inside history) for helpdesks And no extraneous wrappers or tags are introduced that change line spacing by more than one line from preview
Analytics Sync-Back and A/B Attribution
Given a successful insert event occurs When the insert completes Then EchoLens records variant_id, template_id (if any), channel, conversation_id/thread_id, composer_message_id (if available), user_id, and inserted_at timestamp within 3 seconds And the event includes the correct A/B cohort or variant label for attribution And the analytics service confirms persistence with a 2xx status or queues the event for retry with exponential backoff up to 3 attempts
Offline and Permission Error Fallbacks
Given the integration is offline, unauthorized, or rate-limited When the user attempts to insert Then no partial or duplicate content is written to the composer And the UI offers "Copy to clipboard" and "Download reply" fallbacks within 1 second And a non-blocking notification explains the issue and provides a re-authentication link And the attempt is logged with failure_reason and fallback_used=true
Idempotent Insert and Duplicate Prevention
Given a user double-clicks insert or an automatic retry occurs within 60 seconds for the same conversation and variant hash When insertion is processed Then only one instance of the variant content exists in the draft And subsequent identical attempts are ignored or collapsed without altering the draft body And telemetry records deduplicated=true for suppressed attempts
Privacy, Safety, and Compliance Guardrails
"As a compliance officer, I want automated guardrails on generated replies so that we avoid sharing sensitive or misleading information."
Description

Implement prompt/output redaction for sensitive data, policy checks against prohibited claims (e.g., firm dates, unreleased features), and allow/deny phrase lists configurable by admins. Provide per-tenant model isolation options and retention controls compliant with SOC 2 and GDPR. Log generation events with access controls and reviewer override workflow requiring justification and recording of changes.

Acceptance Criteria
Sensitive Data Redaction in Prompts and Outputs
Given a user composes a reply in Reply Variants containing an email, phone number, physical address, credit card number, or API key, When variants are generated, Then those substrings are redacted in model input, model output, and stored logs using [REDACTED:EMAIL], [REDACTED:PHONE], [REDACTED:ADDRESS], [REDACTED:CC], or [REDACTED:SECRET] masks. Given redaction is applied, When data is persisted or synced to Jira/Linear, Then only redacted content is stored or transmitted and no original sensitive substrings are retained. Given inline rationale is generated alongside variants, When rationale includes sensitive substrings, Then the same redaction masks are applied before display and storage.
Policy Check Blocking Prohibited Claims
Given policy rules prohibiting firm release dates and mentions of unreleased features, When a generated variant contains an absolute date claim (e.g., "on August 30, 2025") or references a feature marked Internal in the feature registry, Then the variant is blocked pre-delivery and the user sees an error banner with reason code POLICY_DATE_CLAIM or POLICY_UNRELEASED_FEATURE. Given a block occurs, When the user expands details, Then offending spans are highlighted and sensitive data remains redacted; blocked content is not inserted into the editor. Given hedged/non-committal language (e.g., "targeting late Q4", "we're exploring X") that is not in the deny list, When generated, Then it is allowed. Given multilingual content (EN, ES, FR), When policy check runs, Then detection and blocking behavior is equivalent across supported languages.
Admin-Configurable Allow/Deny Phrase Lists
Given an Admin adds phrases to the Deny list (e.g., "guaranteed delivery date", "we will ship on") and saves, When new variants are generated within the tenant, Then any variant matching a Deny phrase is blocked within 60 seconds of the change being saved. Given a phrase exists on both Allow and Deny lists, When the Allow entry has Override=true, Then the Allow list takes precedence; otherwise the Deny list takes precedence. Given regex/wildcard support is enabled for Admins, When an Admin adds deny: "release on \\b[A-Z][a-z]+ \\d{1,2}, \\d{4}\\b", Then any matching variant is blocked. Given audit logging of configuration changes, When an Admin creates, edits, or deletes a list entry, Then a versioned record is stored with actor, timestamp, change diff, and reason; the Admin can roll back to a prior version. Given tenant isolation, When Admin from Tenant A updates lists, Then the changes affect only Tenant A.
Per-Tenant Model Isolation Configuration
Given a Tenant Admin selects Model Isolation = Shared, Dedicated, or Customer-Managed Endpoint and saves, When subsequent generations occur, Then requests route to the configured provider/project and include the tenant ID in request metadata; provider dashboards show the expected project/API key. Given Dedicated isolation is selected, When any tenant other than the owner generates content, Then their traffic does not use the same provider project or API key. Given Customer-Managed Endpoint is configured, When credentials are rotated or invalid, Then generations fail closed with a clear error and secrets remain encrypted at rest and masked in the UI. Given the isolation setting is changed, When a new generation is initiated, Then the new routing takes effect within 5 minutes and the change is recorded in the audit log.
Data Retention Controls and GDPR/SOC 2 Compliance
Given a Tenant Admin sets content and log retention to 30 days, When 30 days elapse, Then prompts, outputs, and generation logs older than 30 days are permanently deleted within 24 hours and are no longer retrievable via UI or API. Given retention is set to 0 days, When a generation completes, Then prompts and outputs are discarded immediately; only aggregate, non-identifying metrics persist. Given a GDPR DSAR deletion request for a data subject, When submitted via API or UI, Then all associated prompts, outputs, and logs are purged within 30 days and the action is auditable. Given region selection (EU or US) is set per tenant, When data is stored or model calls are made, Then data residency is honored for both storage and processing. Given encryption controls, When data at rest and in transit are inspected, Then AES-256 at rest and TLS 1.2+ in transit are enforced with documented key rotation evidence.
Generation Event Logging with Access Controls
Given a reply generation event occurs, When the system logs the event, Then the record includes tenant ID, user ID, timestamp, variant IDs, model ID, policy check outcomes, redaction counts by type, and sync status to Jira/Linear. Given role-based access controls, When a Viewer attempts to access generation logs, Then access is denied; only Admin and Compliance Reviewer roles can view event metadata. Given multi-tenant isolation, When querying logs via UI or API, Then only events for the requester's tenant are returned. Given tamper-evident storage, When a log entry is modified, Then the original remains immutable and a new version is created with a cryptographic hash chain linking versions. Given search and filtering, When filtering by reason code or date range across up to 10,000 records, Then results return within 3 seconds.
Reviewer Override Workflow with Justification and Recording of Changes
Given a variant is blocked by policy, When a user with the Compliance Reviewer role requests an override, Then the system requires a justification of at least 15 characters and selection of a reason code before submission. Given dual-approval is enabled, When an override is requested, Then a second distinct Reviewer must approve before the variant is released; both actions are logged with actor and timestamp. Given an override is approved, When the variant is released, Then the blocked content and final content (if edited) are stored with a redline diff and any sensitive data remains redacted. Given an override is denied, When the requester is notified, Then the notification includes the denial reasons and links to relevant policy. Given override analytics export, When an Admin exports overrides for a date range, Then the CSV includes requester, approver(s), reason codes, affected rules, timestamps, and outcomes.

SLA Cadence

Schedules polite nudges and escalations if no acknowledgment within your SLA, respecting quiet hours and on-call rules. Reduces churn risk by ensuring every customer gets timely updates without manual follow-ups.

Requirements

SLA Policy Builder
"As a CX lead, I want to define acknowledgment SLAs by channel and severity so that nudges and escalations trigger at the right time for each customer."
Description

Configurable policy engine to define acknowledgment SLAs by channel (reviews, email, tickets, chats), severity, and customer tier. Supports multiple policies, time zone awareness, business hours and holiday calendars per team, and quiet-hour windows. Policies map to Jira/Linear issue types and priorities and are versioned with rollout previews and “what-if” simulation on historical threads. Policies are attached to threads at ingestion so each conversation or issue carries its SLA context throughout EchoLens.

Acceptance Criteria
Define SLA by Channel, Severity, and Customer Tier
Given the policy builder, When a user creates a policy specifying channel (review|email|ticket|chat), severity (P1–P4), customer tier (Free|Pro|Enterprise), and an acknowledgment target in minutes, Then the policy is saved with a unique ID and the exact target minutes. Given an identical or overlapping condition set exists, When the user attempts to save, Then the system blocks the save and displays an Overlapping condition set error. Given a saved policy, When it is evaluated for an eligible thread, Then the thread stores policy_id, policy_version, sla_target_minutes, and due_at (ISO 8601) equal to the configured target.
Time Zone, Business Hours, and Holiday Calendars per Team
Given a team time zone, business hours schedule, and holiday calendar, When computing due_at for a thread, Then non-business hours and listed holidays are excluded from elapsed time and due_at falls within business hours. Given a DST transition in the team time zone, When computing business minutes across the boundary, Then the total counted business minutes equals the configured target without gaining or losing an hour. Given a 24x7 schedule, When computing due_at, Then wall-clock time is used and holidays are ignored. Given the team updates its holiday calendar, When running preview/simulation, Then new rules are used for preview only and do not mutate due_at on existing threads unless explicit re-evaluation is triggered.
Quiet Hours Enforcement
Given quiet hours windows are defined (e.g., 22:00–07:00 local), When a due_at or scheduled acknowledgment falls inside a quiet window, Then it is deferred to the next permitted business minute. Given an on-call override is enabled for P1, When a P1 thread arrives during quiet hours, Then the quiet hours constraint is bypassed for escalation scheduling and the bypass is recorded in audit logs. Given overlapping or invalid quiet hour windows, When attempting to save the policy, Then validation fails with a descriptive message and the policy is not saved.
Multiple Policies and Conflict Resolution
Given multiple active policies match a thread, When evaluation runs, Then the highest-priority policy (explicit order index) is selected and stored on the thread. Given two policies share the same priority, When attempting to save the second policy, Then the system blocks the save and requires a distinct order index. Given a user reorders policies, When the changes are saved, Then the new order is used for evaluations within 1 minute and reflected in the UI. Given a policy is disabled, When evaluation runs, Then the disabled policy is excluded from consideration.
Mapping to Jira/Linear Issue Types and Priorities
Given a policy includes an external mapping, When a new thread triggers issue creation/sync, Then the external issue type and priority are set according to the policy mapping. Given the selected external type or priority is invalid or unavailable, When saving the policy, Then validation fails with an actionable error and prevents save. Given mapping changes after issues were created, When no re-sync is requested, Then existing issues remain unchanged; When re-sync is requested, Then updated mappings apply on the next sync and changes are logged.
Versioning with Rollout Preview and What‑If Simulation
Given an active policy version v1, When a user creates a new draft v2, Then v1 remains Active, v2 is Draft with a unique version label and change note. Given a draft v2, When the user runs a rollout preview for a selectable date range (e.g., last 30 days), Then the system returns: count of impacted threads, delta in SLA breach rate (%), and a list of top affected themes, without changing live evaluations. Given a draft v2, When the user runs a what-if simulation on historical threads for a defined time window, Then for each thread the system computes the policy that would apply, the hypothetical due_at, and breach outcomes, and allows CSV export. Given the user publishes v2, Then v2 becomes Active, v1 becomes Archived and immutable, and new evaluations use v2 within 1 minute.
Policy Attachment at Ingestion and Lifecycle Persistence
Given a new thread is ingested, When policy evaluation runs, Then the chosen policy_id, policy_version, evaluation_timestamp, sla_target_minutes, and due_at are attached to the thread metadata and made available to downstream services. Given policies change after thread ingestion, When no manual re-evaluation is requested, Then the thread retains its originally attached policy/version and due_at. Given a user triggers Re-evaluate SLA on a thread or segment, Then current active policies are applied, fields are updated, and an audit log records previous versus new values including reason and user.
Intelligent Nudge Scheduler
"As a product operations manager, I want reminders to be scheduled automatically and in-context so that customers receive timely updates without my team manually chasing each thread."
Description

Event-driven scheduler that creates polite reminder jobs prior to and at SLA thresholds, with channel-specific timing and retry rules. Respects policy constraints, deduplicates reminders across linked channels, and rate-limits per customer and assignee. Integrates with email, Slack/Teams, Intercom/Zendesk comments, and Jira/Linear comments to send updates in-context. Cancels or reschedules jobs when thread state changes, writes structured logs, and provides backoff to avoid notification fatigue.

Acceptance Criteria
Pre-SLA Nudge Scheduling and Delivery Across Channels
Given a customer thread linked to email, Slack/Teams, Intercom/Zendesk, and Jira/Linear with an SLA due at T and a policy specifying channel timings (email: T-30m, Slack/Teams: T-15m, comments: T-10m, threshold: T) When the scheduler evaluates the thread Then reminder jobs are created for each configured channel at the exact policy offsets within ±60 seconds tolerance And only channels enabled for the tenant and thread are targeted And each sent nudge includes the thread link, SLA due time, and assignee mention appropriate to the channel And for email, the message is sent to the assignee's email with subject prefix "[EchoLens]" and includes the thread title and link And for Slack/Teams, the message is posted as a DM to the assignee or in the configured channel with an @-mention And for Intercom/Zendesk, a comment is posted on the conversation per policy (public or internal) including SLA due time and request for acknowledgment And for Jira/Linear, a comment is posted on the linked issue including SLA due time and a prompt for acknowledgment
Quiet Hours and On-Call Compliance
Given tenant quiet hours are 20:00–08:00 local and an on-call escalation rule is enabled When a reminder is due within quiet hours Then the nudge is deferred to 08:00 local unless it is marked as an escalation with override And if override, the nudge routes to the current on-call channel/user instead of the primary channel And no notifications are sent to non–on-call users during quiet hours And the deferral or override is logged with reason=QuietHours and includes nextEligibleAt
Cross-Channel Deduplication and Idempotency
Given a thread is linked to multiple channels and multiple scheduling events occur within a 5-minute window When the scheduler enqueues reminder jobs Then only one nudge is sent per thread per window across all channels using the configured channel priority And duplicate enqueue attempts are ignored using an idempotency key of tenantId+threadId+windowStart And if the selected channel delivery fails definitively, the next priority channel is attempted exactly once And no more than one message is delivered across channels for the same window
Per-Customer and Per-Assignee Rate Limiting
Given rate limits are configured as max 3 nudges per customer in a 24h rolling window and max 5 nudges per assignee per hour When an additional nudge would exceed either limit Then the scheduler suppresses the nudge And the suppression is logged with reason=RateLimit including current counts and reset times And metrics for sent and suppressed nudges are incremented accordingly
State-Change Cancellation and Reschedule
Given a thread has pending reminder and escalation jobs When the thread is acknowledged, updated by the assignee, or resolved before T Then all pending jobs for that thread are canceled within 60 seconds And an audit log entry is recorded with action=Cancel, priorScheduledTimes, actor=System, and newState And if the thread is re-opened, a new schedule is generated from the re-open timestamp according to the active policy
Structured Logging and Traceability
Given any scheduler action (enqueue, execute, retry, suppress, cancel) When the action occurs Then a structured log entry is emitted containing tenantId, threadId, jobId, policyId, eventType, channel, scheduledAt, executedAt or suppressedAt, outcome, suppressionReason (if any), retryCount, correlationId, and actor And logs are available for query within 2 seconds of the action and retained for at least 30 days And each delivered nudge includes a correlationId that matches the originating job log
Exponential Backoff and Fatigue Protection
Given a transient delivery failure occurs for a nudge When the scheduler retries Then retries use exponential backoff with jitter (e.g., ~5m, ~15m, ~45m with ±20%) up to 3 attempts per job And no more than 1 nudge per thread is delivered within any 12-hour window unless it is the first escalation at the SLA threshold And if fatigue thresholds would be exceeded, the job is suppressed with reason=Fatigue and nextEligibleAt set per policy And final outcomes (sent, suppressed, exhausted) are logged with retryCount and next steps
Cross-Channel Acknowledgment Detection
"As a support lead, I want the system to recognize when we’ve acknowledged a customer across any tool so that redundant reminders stop and SLA reporting stays accurate."
Description

Reliable detection of acknowledgments from agents across channels (email replies, helpdesk notes, chat messages) and from Jira/Linear status or comment activity. Combines rules (keywords, tags, status transitions) with normalization to ignore auto-replies and signatures. Idempotent marking of a thread as acknowledged halts pending nudges and escalations and records the responder and timestamp for SLA reporting.

Acceptance Criteria
Email Reply Acknowledgment Recognition
Given a customer thread with an active SLA and an assigned agent When the agent sends a direct email reply and the newly authored content (excluding quoted text and signatures) contains any term from the acknowledgment keyword list (case-insensitive) or the message has the "acknowledged" tag applied Then within 2 minutes the system marks the thread as Acknowledged and sets acknowledged_at (UTC ISO 8601) and acknowledged_by (agent id/email) and acknowledged_by_source = "email" and emits an audit event "thread.acknowledged" and cancels all scheduled nudges and escalations for the thread (jobs status = canceled) and sends no further SLA nudge messages for the thread
Helpdesk Note Acknowledgment Recognition
Given a customer thread linked to a helpdesk ticket When an agent adds a public reply or an internal note on the ticket that either contains an acknowledgment keyword or applies the "ack" label/tag Then within 2 minutes the system marks the thread as Acknowledged and sets acknowledged_at (UTC ISO 8601), acknowledged_by (agent id/email), acknowledged_by_source = "helpdesk", emits an audit event "thread.acknowledged", and cancels all scheduled nudges and escalations for the thread
Chat Message Acknowledgment Recognition
Given a customer thread with an associated live chat conversation When a human agent (bot flag = false) sends a chat message that matches the acknowledgment keyword list or uses the "Acknowledged" macro/quick-reply Then within 2 minutes the system marks the thread as Acknowledged and sets acknowledged_at (UTC ISO 8601), acknowledged_by (agent id), acknowledged_by_source = "chat", emits an audit event "thread.acknowledged", and cancels all scheduled nudges and escalations for the thread
Jira/Linear Activity-Based Acknowledgment
Given one or more customer threads are linked to a Jira or Linear issue and at least one linked thread has an active SLA When the issue transitions to an in-progress state (e.g., "In Progress", "Doing") or a human user adds a comment containing an acknowledgment keyword Then within 2 minutes the system marks all linked threads with an active SLA as Acknowledged and sets acknowledged_at (UTC ISO 8601) to the event time and acknowledged_by to the user who performed the transition/comment and acknowledged_by_source = "jira" or "linear" accordingly and emits an audit event "thread.acknowledged" for each affected thread and cancels all scheduled nudges and escalations for each affected thread And issue transitions back to a non-in-progress state must not revert the Acknowledged state of any thread
Idempotent Acknowledgment and Cross-Channel De-duplication
Given a customer thread already marked as Acknowledged with acknowledged_at set When any subsequent acknowledgment event arrives from email, helpdesk, chat, Jira, or Linear Then the system does not update acknowledged_at or acknowledged_by, does not emit a duplicate "thread.acknowledged" event, does not re-trigger SLA state changes, and keeps all nudge/escalation jobs canceled And the acknowledgment count for the thread remains 1 and the earliest valid acknowledgment determines the recorded responder and timestamp
Auto-Reply and Signature Normalization
Given an inbound message associated with a customer thread When the message has auto-reply indicators (e.g., Auto-Submitted: auto-replied, X-Autoreply, Precedence: bulk/list, common OOO phrases), is from a no-reply sender, or contains only signatures/quoted history above the reply delimiter Then the system must not mark the thread as Acknowledged and must record reason = "ignored_auto_reply_or_signature" in processing logs and must not emit a "thread.acknowledged" event and must leave existing nudge/escalation schedules unchanged And if the message contains both auto-reply elements and new human-authored text above the quote delimiter, only the human-authored portion is evaluated against acknowledgment rules
Responder Attribution and Timestamp for SLA Reports
Given a customer thread marked as Acknowledged via any channel When SLA reporting views or exports are generated Then acknowledged_at equals the first valid acknowledgment detection time in UTC ISO 8601 and acknowledged_by_id and acknowledged_by_display are populated and acknowledged_by_source is one of ["email","helpdesk","chat","jira","linear"] and the fields are read-only/immutable post-write and null when no acknowledgment exists And the report flags SLA Met = true when acknowledged_at is within the configured SLA window and false otherwise
Quiet Hours & On‑Call Compliance
"As a CX manager, I want nudges and escalations to respect quiet hours and on-call schedules so that we stay polite to customers and only wake the right people when needed."
Description

Scheduling guardrails that defer or reroute communications during customer and team quiet hours while honoring on-call rules. Looks up customer locale and time zone, internal business-hour calendars, holidays, and do-not-contact flags. Provides severity-based overrides, next-best-send time calculation, and integration with PagerDuty/Opsgenie schedules to ensure escalations target available responders only. All suppressions and overrides are logged for auditability.

Acceptance Criteria
Customer Quiet Hours Deferral & Next-Best Send
Given a customer's time zone and quiet hours are configured as 21:00–07:00 local When a customer-facing nudge is generated at 22:15 local Then the system does not send the message And it schedules delivery at 07:00 local on the next allowable business day And the scheduled time is displayed in the nudge queue within 1 second of scheduling Given the computed next-best-send time would occur after the SLA deadline When the severity does not allow override Then the message remains deferred And the item is flagged "At Risk of SLA Breach" with a visible badge
Do-Not-Contact and Holiday Enforcement
Given a customer has a Do-Not-Contact flag set to true When any outbound communication is queued Then no message is sent And the item is marked "Suppressed: DNC" And a suppression log entry is created with reason=DNC Given the customer's locale has a public holiday on the current date When a nudge would fall within that date's quiet/holiday window Then the send is deferred to the next business day at the start of allowable hours And the audit log records reason=Holiday
Severity-Based Override of Quiet Hours
Given severity is Critical and the next-best-send time would occur after the SLA deadline When a nudge is generated during customer quiet hours Then the system bypasses quiet hours and sends immediately And it records an override log with severity=Critical and approver=System Given severity is Normal and the next-best-send time would occur after the SLA deadline When a nudge is generated during quiet hours Then the system does not bypass quiet hours And it flags the item "At Risk of SLA Breach" And it notifies the internal on-call channel instead of the customer
On-Call Routing via PagerDuty/Opsgenie
Given an escalation must be sent to an internal team And PagerDuty or Opsgenie returns an active on-call user for the target schedule at the current timestamp When the escalation is triggered Then only the active on-call assignee(s) receive the notification And users not on duty do not receive it Given the integration returns no active on-call user When an escalation is triggered Then the system routes to the escalation policy fallback contact And records a log with reason=NoOnCallFound
Internal Quiet Hours Compliance
Given the internal business-hours calendar is configured as 09:00–18:00 Mon–Fri in team local time When an internal nudge is scheduled at 02:00 team local time and severity is not Critical Then the system defers the internal notification to 09:00 next business day And displays the scheduled time in the internal queue Given severity is Critical When an internal escalation occurs during internal quiet hours Then the system delivers to on-call only And does not notify non on-call members
Auditability of Suppressions and Overrides
Given any suppression or override occurs When the event is processed Then an audit record is written containing: event_id, timestamp_utc, customer_id, channel, direction, severity, rule_applied, reason_code, actor(system or user), original_send_time, new_send_time(if deferred), override_flag(boolean), integration_source(PagerDuty/Opsgenie/None) And the record is retrievable via the audit API within 5 seconds Given an audit query by time range and customer_id When requested via the UI or API Then results include all matching suppression/override records And each record is immutable and shows who approved overrides if any
Channel-Specific Quiet Hours Enforcement
Given channel quiet rules are configured as SMS: 20:00–08:00 and Email: 22:00–07:00 in customer local time When two nudges (one SMS, one Email) are queued at 21:00 local Then the SMS nudge is deferred to 08:00 next business day And the Email nudge is sent immediately And both outcomes are reflected in the queue statuses within 2 seconds
Multi‑Level Escalation Routing
"As an incident coordinator, I want breaches to escalate through predefined tiers so that the right stakeholders are alerted promptly and ownership is clear."
Description

Configurable escalation tiers with recipients (assignee, team channel, manager, on-call), triggers at breach or prolonged silence, and per-tier timers. Supports channel-specific messages, conditional routing by customer tier or theme severity, and actions such as reassigning Jira/Linear issues, adding watchers, or raising PagerDuty incidents. Includes manual snooze/override, retry with exponential backoff, and complete audit trail of who was paged and when.

Acceptance Criteria
Escalation Triggers: SLA Breach and Prolonged Silence
Given an open case with configured SLA breach time and a silence window for acknowledgment When the current time reaches or exceeds the breach time and no acknowledgment or resolution is recorded Then the system sends the configured Tier 1 escalation within 60 seconds and records the event And the same Tier 1 escalation is not sent more than once per case per breach event When the silence window elapses prior to breach with no acknowledgment Then the system triggers the configured pre-breach escalation within 60 seconds and records the event And if acknowledgment occurs before either trigger, no escalation is sent
Per‑Tier Timers and Stop Conditions
Given multiple escalation tiers with per-tier timers and recipients defined When Tier N is sent for a case Then a timer for Tier N+1 starts for the configured duration And if acknowledgment or resolution occurs before the Tier N+1 timer expires, no further tiers are sent And if no acknowledgment or resolution occurs by expiry, Tier N+1 sends within 60 seconds And each tier fires at most once per case
Conditional Routing by Customer Tier and Theme Severity
Given routing rules that map customer tier and theme severity to recipients and channel-specific message templates When a case is Gold and severity is High Then the escalation routes to the Gold/High recipients with the High-severity templates on each channel When a case is Standard and severity is Low Then the escalation routes to the Standard/Low recipients with the Low-severity templates And channel-specific tokens (e.g., Slack @mentions, email subjects) are applied correctly per channel
Action Integrations on Escalation (Jira/Linear, Watchers, PagerDuty)
Given escalation actions include reassigning the linked Jira/Linear issue, adding watchers, and creating a PagerDuty incident When the tier fires Then the linked issue is reassigned to the intended assignee within 60 seconds And the specified users are added as watchers/followers And a PagerDuty incident is created with configured severity, summary, and correlation key, assigned to the current on-call user for the target schedule And external action results (IDs, success/failure) are recorded
Manual Snooze and Override Behavior
Given an upcoming or active escalation schedule on a case When a permitted user applies a snooze until a specific datetime Then no escalations are sent during the snooze window and all timers are paused And when the snooze expires, timers resume and the next pending tier evaluates immediately When a permitted user applies an override to mark the case acknowledged or resolved Then all pending timers are canceled and no further tiers are sent And snooze/override actions are recorded with actor, reason, and timestamp
Retry with Exponential Backoff on Failed Sends
Given an escalation send attempt fails for a channel with a transient error When retries are enabled with exponential backoff and a maximum attempts/cap configured Then the system retries with exponentially increasing delays plus jitter until success or limits are reached And on success, no further retries occur for that channel and the send is marked successful And on exhaustion, the failure is surfaced, recorded, and subsequent tiers continue to evaluate on their timers
Complete and Immutable Audit Trail
Given escalations, acknowledgments, overrides, snoozes, retries, and external actions occur on a case When viewing the case audit trail Then each event includes timestamp (UTC), actor (user or system), tier, channel, recipients, outcome, external IDs, and correlation ID or payload hash And events are immutable and strictly ordered by time And the audit trail is filterable by event type and exportable to JSON and CSV
SLA Monitoring Dashboard & Audit Trail
"As a product manager, I want visibility into upcoming risks and past SLA performance so that I can allocate resources and improve our response processes."
Description

Real-time dashboard showing threads by SLA stage (safe, nudging, at risk, breached), time to next action, and owner. Filters by channel, team, customer tier, and theme. Historical reports include acknowledgment times, nudge effectiveness, and escalation outcomes with CSV export. Each thread exposes a detailed audit log of applied policy, scheduled and sent messages, suppressions, acknowledgments, and user overrides.

Acceptance Criteria
Real-Time SLA Stage Overview and Metrics
Given a workspace with SLA-tracked threads across Safe, Nudging, At Risk, and Breached When the dashboard loads Then stage counts match backend counts for the current filters And each thread row displays Owner, SLA Stage, and Time to Next Action in hh:mm or mm:ss And Time to Next Action displays "Due now" when <= 0 and is highlighted When a thread’s stage or owner changes in the backend Then the dashboard reflects the change within 15 seconds without manual refresh And a manual refresh updates immediately
Filter by Channel, Team, Customer Tier, and Theme
Given threads span multiple channels, teams, customer tiers, and themes When the user applies Channel=Email Then only Email threads are listed and per-stage counts recalc accordingly When the user adds Team=Support and Customer Tier=Enterprise and Theme=Billing Then results satisfy all filters (logical AND) and counts match the list When filters yield zero results Then an empty state appears with a clear-filters action When the user navigates away and back Then previously applied filters persist in URL and are restored
Thread Drill-Down with SLA Metadata
Given the dashboard list is filtered When the user opens a thread from the list Then a detail view opens showing Current Stage, Owner, Time to Next Action, and Policy Name And a deep link URL is generated for the thread detail When the user returns to the dashboard Then prior filters and scroll position are preserved
Historical Report: Acknowledgment Times
Given a selectable date range and filters When the user runs the Acknowledgment Times report Then the report computes count, mean, median, p90, and max acknowledgment time in minutes And acknowledgment time is defined as time from first customer message in thread to first agent acknowledgment And metrics are groupable by Channel, Team, Customer Tier, and Theme And totals and group subtotals equal the sum of underlying rows
Historical Report: Nudge Effectiveness and Escalation Outcomes with CSV Export
Given SLA nudges and escalations occurred in the selected date range When the user runs the Nudge & Escalation report Then nudge effectiveness is calculated as percent of nudged threads acknowledged within SLA within 24 hours of the nudge And escalation outcomes include counts and rates of resolved, acknowledged, and breached after escalation And results can be grouped by Channel, Team, Customer Tier, and Theme When the user clicks Export CSV Then a CSV downloads within 5 seconds containing the visible grouping plus per-thread rows with Thread ID, Stage at event, Event Timestamps, Outcome, and Owner
Per-Thread Audit Log Completeness and Integrity
Given a thread under SLA cadence When the user opens the Audit Log section Then entries include Applied Policy (name and version), Scheduled Messages (type and planned time), Sent Messages (type, channel, and sent time), Suppressions (reason and window), Acknowledgments (actor and time), and User Overrides (actor, field changed, old→new, and reason) And each entry shows an ISO 8601 timestamp with timezone and an actor (system or user) And entries are ordered newest-first and paginated when more than 50 When a suppression or override occurs Then the corresponding audit entry appears within 10 seconds And audit entries are read-only; edit actions are unavailable
Message Templates & Personalization
"As a CX content owner, I want reusable, localized templates for reminders and escalations so that messages are consistent, on-brand, and effective across channels."
Description

Central library of polite, brand-safe templates for nudges and escalations with variables (customer name, ticket ID, theme, SLA times) and localization support. Includes preview, role-based editing, and approval workflow. Integrates with CRM data for personalization and inserts compliance elements (unsubscribe/opt-out where applicable). Supports A/B testing to improve response rates without increasing notification fatigue.

Acceptance Criteria
Role-Based Template Authoring and Approval
- Given a user with Viewer role, when they attempt to create, edit, approve, or publish a template, then the action is blocked and an authorization error is shown. - Given a user with Editor role, when they create or edit a template, then the template is saved in Draft state and cannot be used for sending until approved. - Given a user with Approver role, when they approve a Draft, then the state changes to Approved and can be Published by an Approver. - Given an Approved template, when it is Published, then only the latest Published version is available for selection by automations; Drafts remain unavailable. - Given a Published template v1, when a new Draft v2 is created and later Published, then v2 supersedes v1 and the version history logs author, timestamp, and diff. - Given a template in any state, when an unauthorized role attempts a restricted transition, then the transition is rejected and logged in the audit trail. - Given two Editors editing the same Draft, when both attempt to save, then optimistic locking prevents overwrite and prompts the second saver to resolve conflicts.
Variable Substitution and CRM Fallbacks
- Given a template containing variables {{customer_name}}, {{ticket_id}}, {{theme}}, and {{sla_due_time}}, when rendered with a ticket linked to CRM contact, then the variables resolve with CRM values and ISO-to-local time formatting for sla_due_time. - Given a template containing an undefined variable, when saving, then validation fails and highlights the unknown token. - Given a template variable with no corresponding CRM value, when rendered, then the defined fallback (e.g., {{customer_name|there}}) is used; if no fallback is provided, a default safe placeholder is inserted and a warning is logged. - Given CRM is temporarily unavailable, when rendering a preview, then the system uses cached ticket/context data if available or shows placeholder tokens without blocking save; sending is queued until data resolves. - Given a template with conditional logic (e.g., show account_tier-specific line), when the condition is not met, then the block is omitted without leaving extraneous spacing. - Given an attempt to send, when any required variable is unresolved, then the send is blocked with a clear error listing missing fields.
Localization and Locale Fallback
- Given a contact with preferred_language=fr-FR in CRM, when a multilingual template family exists (en-US default, fr-FR translation), then the fr-FR variant is selected for render and send. - Given a contact with preferred_language=es-ES but no es-ES translation exists, when rendering, then the system falls back to the default locale en-US and logs a locale fallback event. - Given right-to-left locales (e.g., ar), when previewing and sending, then text direction is RTL and punctuation/numerals render correctly. - Given localized date/time tokens, when rendered, then formats use the locale conventions (e.g., 24-hour vs 12-hour, month/day order) and the recipient’s time zone from CRM. - Given a translation import file, when uploaded, then the system validates placeholders parity across locales and rejects translations missing required variables.
Preview and Test Send Accuracy
- Given a selected ticket and recipient, when clicking Preview, then the preview displays the exact final message including variable resolutions, locale formatting, subject, body, and compliance elements. - Given a test send target (internal sandbox email/SMS/Slack channel), when Test Send is triggered, then the recipient receives an identical message to production except clearly labeled as Test and excluded from metrics. - Given a template with conditional blocks, when toggling sample data in preview, then visible sections update in real time without caching artifacts. - Given a template exceeding channel limits (e.g., SMS 160 chars per segment), when previewing, then an estimate of segments and truncation alerts are shown. - Given a broken link in the template, when validating before test send, then link validation flags the URL with status code/error.
Compliance Elements and Suppression
- Given an email channel template, when rendering, then a compliant footer including company name, physical address, and unsubscribe link is automatically appended and cannot be removed by non-admin roles. - Given an SMS channel template, when rendering, then STOP/HELP instructions are included on first message in a thread and at least every 30 days thereafter. - Given a recipient who has opted out or is on a suppression list, when attempting to send, then the message is not sent, the event is logged with reason, and no further retries are scheduled. - Given jurisdiction data (e.g., country/region) from CRM, when rendering, then the correct regional legal text is inserted (e.g., CASL vs CAN-SPAM) and consent checks are enforced. - Given a Slack/Teams channel, when rendering, then email/SMS-specific compliance elements are omitted and channel-appropriate disclaimers are used if configured.
A/B Testing and Winner Selection
- Given a template family with variants A and B, when activation criteria are met, then traffic is split by the configured ratio without sending more total messages than the control would. - Given per-recipient bucketing, when a recipient receives variant A, then they are excluded from variant B for the same experiment across all SLA steps. - Given reply rate and click rate metrics, when the experiment reaches minimum sample size and significance threshold, then the winner is auto-selected and losing variant is paused. - Given manual override by an Approver, when the winner is set manually, then the system records the reason and applies the winner to future sends. - Given experiment reporting, when viewed, then it shows sends, opens (email), clicks, replies, opt-outs, confidence interval, and decision status for each variant.
Channel and SLA-Step Targeting
- Given templates tagged by channel (email, SMS, Slack) and SLA step (Nudge 1, Nudge 2, Escalation), when an automation triggers a step, then the system selects the highest-priority Published template matching both tags; if none match, it falls back to the channel default. - Given quiet hours and on-call rules are active, when a template is selected, then the scheduled send time complies with these rules and the preview displays the planned send window. - Given multiple locales within an SLA step, when selecting a template, then locale and channel filters are applied before A/B variant allocation. - Given a deprecated template for a step, when selection occurs, then deprecated templates are excluded from consideration and a warning is shown if no valid option remains.

Loop Metrics

Tracks opens, replies, acknowledgment time, sentiment shift, and renewal impact per theme and segment. Surfaces what wording, channels, and ETAs work best, with shareable digests that prove the value of closing the loop.

Requirements

Cross-channel Loop Event Tracking
"As a CX lead, I want all outreach and response events captured across channels so that I can measure engagement and response effectiveness by theme and segment."
Description

Implement robust instrumentation across email providers, helpdesk/chat tools, and in‑app notifications to capture loop events (open, click, reply, delivery, bounce) and associate each event with message metadata (theme, wording template, channel, promised ETA, sender, recipient, timestamps). Ensure reliable ingestion via APIs and webhooks for systems such as Gmail/Office 365, Zendesk, Intercom, Help Scout, Slack, and MS Teams, with idempotency, de‑duplication, and rate‑limit handling. Persist events in a time‑series store with links to user/account IDs and EchoLens themes, supporting real‑time processing and backfill. Provide privacy controls and opt‑out handling, and expose an internal API for downstream analytics.

Acceptance Criteria
Email Event Tracking via Gmail and Office 365
Given an outbound loop email sent through EchoLens with metadata (theme_id, wording_template_id, channel=email, promised_eta, sender_id, recipient_id, message_id) and linked user_id/account_id When Gmail and Office 365 deliver delivery, open, click, reply, and bounce events via APIs/webhooks for that message Then each event is ingested once, normalized to {event_type, provider, channel, timestamp_utc, message_id, provider_event_id}, associated to the original metadata and user_id/account_id/theme_id, and persisted to the time-series store And the internal analytics API GET /internal/events?message_id={id} returns the new event within 2 seconds (p95) of webhook receipt And event timestamps are stored in UTC with millisecond precision, and provider-to-internal clock skew after normalization is ≤ 2 minutes
Helpdesk, Chat, and Collaboration Event Tracking (Zendesk, Intercom, Help Scout, Slack, MS Teams)
Given loop messages sent via Zendesk, Intercom, Help Scout, Slack, and MS Teams with a common correlation key (message_id or external_id) and required metadata When provider webhooks emit delivery/read/open, click, reply, and failure/bounce-equivalent events Then events are mapped to standard event types {delivered, opened, clicked, replied, bounced} with provider metadata retained, associated to theme_id/user_id/account_id, and persisted And for Slack/MS Teams, replies in threads are captured and linked via thread_id/message_id to the originating loop And the internal API can filter events by provider in {Slack, MS Teams, Zendesk, Intercom, Help Scout} and event_type, returning correct counts for a 15-minute window that match provider APIs within ±1%
Idempotency and De-duplication Across Retries
Given duplicate webhook deliveries for the same provider event carrying the same (provider_event_id, message_id) When the system processes these duplicates concurrently or sequentially up to 10 times Then exactly one event record exists in the time-series store, and all duplicates are acknowledged without error And the internal API returns a count of 1 for that (message_id, event_type) combination And idempotency keys are retained for at least 24 hours to prevent re-ingestion across delayed retries
Rate-Limit Handling and Retry Backoff
Given provider APIs respond with HTTP 429 and a Retry-After header during event fetch/backfill When the system encounters rate limits Then it applies exponential backoff with jitter honoring Retry-After, does not exceed provider limits, and retries until success or exhausted after 5 attempts And no events are lost; p95 processing lag during rate limiting remains under 5 minutes, and backlog is fully drained within 15 minutes after limits lift And operational metrics expose current retry counts and lag via /internal/metrics
Time-series Persistence, Real-time Latency, and Backfill Completeness
Given the time-series store is operational When new events arrive via webhook or poll Then 95th percentile end-to-end latency from receipt to queryability via internal API is ≤ 5 seconds, and each record stores fields: {event_id, event_type, provider, channel, message_id, provider_event_id, theme_id, wording_template_id, promised_eta, sender_id, recipient_id, user_id, account_id, timestamp_utc, ingestion_timestamp} And when a new integration is connected with a 30-day backfill request Then at least 99% of eligible historical events are ingested and deduplicated, and event counts by day per provider match source APIs within ±1% after backfill
Privacy Controls and Opt-Out Enforcement
Given a recipient, account, or domain is marked as opt-out in EchoLens When loop messages are sent and provider events (opens, clicks, replies) are received for that identity Then tracking pixels and tracked links are disabled for outbound messages where applicable, and any inbound events for opted-out identities are discarded and not persisted And the internal API omits PII (email, display name) unless the requester has role=analyst or higher, in which case values are hashed or masked per policy And a Delete-My-Data action removes all events for the identity within 24 hours, verified by the internal API returning zero results
Internal Analytics API Filters and Access Control
Given events exist across multiple providers, themes, and accounts When a client queries GET /internal/events with filters (theme_id, account_id, user_id, channel, provider, event_type, time_range, has_reply=true/false, promised_eta_range) and pagination Then the API returns correct, paginated results with total_count, deterministic ordering by timestamp_utc DESC, and responds within 800 ms for result sets ≤ 1,000 rows And authorization enforces least privilege: a token scoped to account_id can only access events for that account, and unauthorized filters return HTTP 403 And the API supports idempotent cursor-based pagination without duplication across pages
Theme and Segment Attribution Engine
"As a product manager, I want communications and metrics tied to the right themes and segments so that performance is comparable and actionable."
Description

Build an attribution service that reliably ties each outreach and response event to the correct EchoLens theme(s) and customer segment(s) (e.g., plan, ARR band, industry, region, persona). Support many‑to‑many mapping with weights and confidence scores, configurable rules and overrides, and versioned mapping so historical analytics remain reproducible when themes evolve. Enable retroactive re‑attribution when auto‑clustering updates, maintain referential integrity to accounts and users, and publish normalized, queryable records for analytics and dashboards.

Acceptance Criteria
Many-to-Many Attribution with Weights and Confidences
Given an outreach or response event E with model matches to themes T1 (confidence 0.82) and T2 (confidence 0.67) and account segments plan=Pro, arr_band=50k-100k, industry=SaaS When the attribution service processes E Then it emits two attribution records (E,T1) and (E,T2) And the weights are proportional to confidences and normalized so weight(T1)+weight(T2)=1.00 And the stored confidence values equal 0.82 and 0.67 with precision to two decimals And the segment dimensions on each record equal the account’s values as of E.occurred_at And no attribution record is created for any theme below the configurable min_confidence threshold
Rules and Manual Overrides Precedence with Auditability
Given a configurable rule R: if channel=email and subject contains "invoice" then theme=Billing with weight=1.0 and source=rule priority=200 And a model suggests theme=Payments (0.76) When an event matches R Then the attribution uses theme=Billing, weight=1.0, confidence is recorded as null, attribution_source=rule, priority=200 And an audit log entry captures rule_id, matched_fields, actor=system, timestamp, and before/after mapping Given a manual override O applied by user U on event E to theme=Contracts with weight=1.0 When E is reprocessed for any reason Then O takes precedence over rules and model, attribution_source=override, actor_user_id=U, and the outcome is unchanged
Versioned Theme Mapping for Reproducible History
Given theme taxonomy version v1 is active at T0 and is replaced by v2 at T1 And events E0..En occurred before T1 and were attributed under v1 When v2 becomes active without triggering retroactive re-attribution Then existing attribution records retain theme_version_id=v1 and remain queryable And rerunning a saved report for [T0, T1) returns the same counts as before the version change And new events after T1 carry theme_version_id=v2
Retroactive Re-Attribution on Auto-Cluster Updates
Given an auto-clustering model update M2 is deployed at T2 with a migration plan to re-attribute events in [T0, T2) When a retro re-attribution job is run for that window Then each affected attribution record is end-dated (valid_to set) and a new record is created with valid_from=T2, theme_version_id=current, and updated weights/confidences And the job produces a change report with counts of updated events, added/removed themes, and net weight deltas by segment And the job is idempotent: rerunning it produces zero additional changes And referential integrity and schema validations pass for 100% of new records
Referential Integrity to Accounts and Users
Given an event E references account_id A and user_id U that exist in the master data When attribution records are created Then foreign keys (account_id, user_id) are valid and enforced And if A or U is missing, the event is moved to a quarantine with error_code=FK_MISSING and no attribution records are emitted And once the missing FK is repaired and E is retried, a single correct set of attribution records is produced
Publish Normalized, Queryable Attribution Records
Given the attribution pipeline processes events When records are written to the analytics store Then each record contains: event_id, event_type, theme_id, theme_version_id, segment_plan, segment_arr_band, segment_industry, segment_region, segment_persona, weight, confidence, attribution_source (model|rule|override), source_version, valid_from, valid_to (nullable), created_at, updated_at, account_id, user_id And records are exposed via a warehouse table/view and a read API endpoint GET /attributions with filters on date range, theme_id, and segment fields And sample queries aggregating weight by theme and segment return results consistent with stored records
Idempotency and Duplicate Event Handling
Given the same source event is ingested twice (same event_id or stable source_hash) When the attribution service processes the second occurrence Then no duplicate attribution records are created and the existing records are left unchanged And if the payload changes, only fields marked updatable (confidence, weight within same version) are updated; event_id and valid_from remain immutable And the service emits a deduplication metric incremented by 1
Acknowledgment Time and SLA Metrics
"As a support manager, I want acknowledgment times by theme and segment so that I can meet SLAs and prioritize operational improvements."
Description

Compute acknowledgment time from inbound feedback to first human acknowledgment and from acknowledgment to promised follow‑up, with channel‑aware logic, business‑hours calendars, and timezone handling. Produce real‑time and historical aggregates (avg, p50/p90/p95) by theme and segment, track SLA targets and breaches, and generate anomaly alerts when metrics regress. Cache precomputed aggregates for fast dashboards and export metrics to the live theme map overlay.

Acceptance Criteria
Compute Channel- and Business-Hour–Aware Acknowledgment Intervals
Given inbound feedback events with channel (email, chat, ticket, review), created_at timestamp, assigned team timezone, and a business-hours calendar And replies/updates with author metadata and timestamps When computing metrics Then the system calculates: - first_ack_time = business-minutes from inbound created_at to the first human acknowledgment per channel rule - ack_to_promise_time = business-minutes from first human acknowledgment to the first recorded promised-follow-up event And business-minutes include only intervals overlapping the assigned team’s business-hours calendar (holidays/weekends excluded) And all timestamps are normalized to the team timezone with correct handling of DST transitions (no double-counting or gaps) And rounding is performed to whole seconds internally; displayed values round to 1 decimal minute And channel rules are: - Email: first reply authored by a human agent (not bot/system) - Chat: first agent message after the user’s message (excluding bot/system) - Ticket: first public agent comment or status change to "Acknowledged" - Review: first owner/agent response on the review platform
Exclude Automated Messages from First Acknowledgment Determination
Given a message stream that may include bot/system messages and auto-responders (e.g., OOO, receipt confirmations) When identifying the first human acknowledgment Then messages flagged is_bot=true or matching standard auto-responder patterns are excluded And only messages authored by active human agents in the workspace are considered And if no human acknowledgment occurs within 14 calendar days, first_ack_time is set to null and the item is flagged no_ack=true (not treated as 0) And audit logs retain the id and timestamp of the message chosen as the first acknowledgment
Real-Time and Historical Aggregates by Theme and Segment
Given feedback items tagged with theme(s) and segment(s) and their computed first_ack_time and ack_to_promise_time When requesting aggregates for a time range (Last 24h, 7d, 30d, 90d, or custom) with filters (theme, segment, channel) Then the API returns for each metric: count, average, p50, p90, p95, and last_updated_at And aggregates include only items whose inbound created_at falls in the requested range And new qualifying items appear in real-time aggregates within 60 seconds of ingestion And results are consistent across API and UI (values match within 1% for the same filters and range) And timezone normalization is applied consistently to grouping and filtering
SLA Target Tracking and Breach Flagging
Given SLA targets are configurable by channel and/or segment for first_ack_time and ack_to_promise_time in business minutes When metrics are computed for each item Then each item is labeled sla_met=true/false per applicable targets And breach_at is recorded as the first business-minute exceeding the target And no promised follow-up within the SLA window results in sla_met=false for ack_to_promise_time And aggregated breach_count and breach_rate are returned by theme and segment for the selected time range And SLA evaluation uses the same business-hours calendar and timezone as metric computation
Anomaly Alerts on Metric Regression
Given rolling baselines per theme, segment, channel, and metric computed as the median over the prior 28 days for the same day-of-week and hour-of-day When any of p90(first_ack_time) or p90(ack_to_promise_time) increases by ≥20% versus its baseline with sample size ≥50 in the last hour Then a regression alert is generated containing metric, current value, baseline, delta %, sample size, dimensions, and link to dashboard And alerts are de-duplicated to at most one per metric+theme+segment+channel per 24 hours And an alert auto-resolves when the current value returns to within 10% of baseline for one hour And alert evaluations run at least every 5 minutes
Precomputed Aggregate Cache for Fast Dashboards
Given precomputation jobs materialize aggregates per theme, segment, channel, and time bucket (hour/day) When a dashboard query requests aggregates for common ranges (24h/7d/30d/90d) Then responses are served from the cache with p95 latency ≤500 ms and p99 ≤900 ms And cache entries carry an as_of timestamp; staleness is ≤90 seconds for Last 24h and ≤5 minutes for longer ranges And on new data ingestion, only affected buckets are invalidated and recomputed within 30 seconds And on cache miss, on-demand computation completes with p95 ≤2 seconds
Export Metrics to Live Theme Map Overlay
Given the live theme map overlay is enabled with a selected time range and filters When "Ack Times & SLA" overlay is toggled on Then each theme node displays first_ack_time p90, average, and breach_rate for the active filters And values match the dashboard/API within 1% for the same time range and filters And overlay data refreshes within 60 seconds of new qualifying data And clicking a theme node opens a detail panel linking to the underlying items and SLA breaches
Sentiment Shift Measurement
"As a product manager, I want to quantify sentiment change after outreach so that I can prove the impact of closing the loop and refine messaging."
Description

Measure sentiment change pre‑ and post‑outreach by applying channel‑specific NLP on conversation threads, CSAT/NPS comments, and ticket updates. Store baseline and post‑interaction sentiment with confidence intervals, support manual overrides, and control for confounders such as time since incident and resolution status. Provide cohort analysis by theme, segment, wording, channel, and ETA, expose deltas with uncertainty bands, and integrate visualizations into the theme map and digests.

Acceptance Criteria
Baseline Sentiment Capture at Ingestion
Given a new user-authored thread is ingested from email, chat, ticket, or review And the thread contains at least one message prior to any outreach event When ingestion completes Then the system selects the channel-specific NLP model for the thread's channel, or falls back to a default if unavailable And computes baseline_sentiment_score in [-1,1], baseline_sentiment_label in {Very Negative, Negative, Neutral, Positive, Very Positive}, and baseline_confidence in [0,1] And stores baseline_model_id and baseline_timestamp And persists these fields to the analytics store within 2 minutes of ingestion completion And exposes them via an API endpoint addressable by thread_id
Post-Interaction Sentiment and Delta Computation
Given an outreach event is logged for a thread And at least one subsequent user response or ticket status update occurs within 14 days When the qualifying response arrives or the post window closes (whichever occurs first) Then the system computes post_sentiment_score, post_sentiment_label, post_confidence, post_model_id, and post_timestamp using the channel-specific model And computes sentiment_delta = post_sentiment_score - baseline_sentiment_score And derives delta_direction in {Improved, No Change, Worsened} using a 0.05 two-sided significance threshold And completes these computations within 5 minutes of the qualifying event
Uncertainty Bands and Display Rules
Given a theme or cohort with sample size n >= 20 qualifying threads When aggregating sentiment_delta at the theme or cohort level Then the system outputs mean_delta, 95% confidence interval [lower, upper], standard error, and sample size n And renders uncertainty bands on the live theme map and in shareable digests And provides a UI toggle to switch between 90% and 95% intervals And for n < 20 displays an insufficient data message and hides uncertainty bands
Manual Sentiment Override with Audit and Recompute
Given an admin user initiates a manual override on baseline or post sentiment for a thread When the override form is submitted with required fields (target=baseline|post, new_score or new_label, reason) Then the system writes an audit record including thread_id, target, previous_value, new_value, reason, actor_id, and timestamp And marks the sentiment value as overridden and uses it in all subsequent computations And recalculates sentiment_delta and all affected aggregates and visualizations within 2 minutes And provides a revert action that restores the previous value and triggers recalculation
Confounder Control in Aggregate Estimates
Given confounders time_since_incident and resolution_status are recorded for each thread When computing aggregate mean_delta for any cohort Then the system produces both unadjusted and adjusted estimates that control for the confounders And labels adjusted results as Adjusted in both UI and API responses And exposes metadata listing confounders used and the adjustment method And uses adjusted estimates by default in the theme map and digests
Cohort Analysis by Theme, Segment, Wording, Channel, and ETA
Given a user applies filters for theme, customer segment, outreach wording variant, channel, and ETA bucket When the cohort analysis is executed Then for each cohort the system returns count n, baseline_mean, post_mean, mean_delta, 95% confidence interval, and significance p_value And supports sorting by mean_delta, n, or impact per account And provides CSV export and a shareable digest link with the same metrics And returns results within 4 seconds for datasets up to 5000 threads
Renewal Impact Attribution
"As a CX lead, I want to understand how closing the loop on specific themes influences renewals so that I can justify investment and target follow‑ups where they drive revenue impact."
Description

Integrate with CRM and billing systems (e.g., Salesforce, HubSpot, Stripe, Chargebee) to ingest renewals, churn, and expansion data, and join outcomes to accounts and the themes they were contacted about. Compute attribution using defined windows and guardrails (minimum sample sizes, cohort matching) and provide correlation and uplift estimates with caveats. Support configurable models (e.g., difference‑in‑differences, propensity scoring), surface confidence scores, and enable exports to BI tools for finance reviews.

Acceptance Criteria
Ingest Renewal Outcomes from CRM and Billing
Given connectors for Salesforce, HubSpot, Stripe, and Chargebee are configured with valid credentials When the scheduled ingestion job runs and a manual "Sync Now" is triggered Then renewal, churn, and expansion events from the last 24 months are ingested with fields: provider, event_id, account_id, customer_id, effective_date, outcome_type, mrr_delta, currency And duplicate events are deduplicated by provider + event_id with idempotent writes And ≥99% of events map to an internal account via CRM account ID or normalized billing customer ID And unmapped or failed records are logged with reason codes and retried up to 3 times; error rate ≤1% per run
Join Outcomes to Contacted Themes Within Configurable Window
Given account-theme contact events exist with timestamps, channel, and theme_id And an attribution window is configured (e.g., 30 days post-contact) with touch model (first-touch, last-touch) When outcome events occur for those accounts Then outcomes falling within the window are associated to the chosen touch model for the theme And tie-breakers are resolved deterministically by latest contact timestamp then channel priority And the join produces a record per account-theme-outcome with window_start, window_end, and join_reason
Guardrails: Minimum Sample Size and Cohort Matching
Given a minimum sample size N (default 30 per cohort) and matching keys (e.g., plan, region, tenure) are configured When computing uplift or correlation for a theme-segment Then results are only displayed if both treatment and control cohorts meet N and pass balance checks (absolute standardized mean difference < 0.1 across keys) And if guardrails are not met, the metric is suppressed and labeled "Insufficient sample" with confidence_score ≤ 0.2
Model Selection: Difference-in-Differences and Propensity Scoring
Given the user selects an attribution model (Difference-in-Differences or Propensity Scoring) and defines pre/post windows When the computation runs Then outputs include: uplift_percent, 95% confidence interval, p_value, sample sizes, and model metadata (window lengths, covariates) And for DiD, pre-period parallel trends are tested; if p_value for slope difference < 0.1, a caveat banner is shown and confidence_score is reduced by ≥0.2 And for Propensity, a propensity model is trained and post-matching balance achieves absolute SMD < 0.1 across covariates; if AUC < 0.6, a "low model fit" caveat is shown and confidence_score ≤ 0.5
Surface Confidence Scores, Correlation, and Uplift with Caveats
Given attribution results exist for theme-segment pairs When viewing the Loop Metrics dashboard or downloading results Then each row displays correlation (Pearson or Spearman as appropriate), uplift_percent, 95% CI, p_value, treatment/control sample sizes, and confidence_score ∈ [0,1] And hovering or details view lists caveats including model assumptions, guardrail status, and data gaps And re-running the same snapshot yields identical results within a tolerance of ±0.1% for metrics and ±0.01 for confidence_score
Export Attribution Results to BI Tools
Given a user has permission to export and selects CSV download or a warehouse connector When an export is requested or a daily schedule is configured Then a dataset with columns [account_id, theme_id, segment, model, outcome_type, window_start, window_end, uplift_percent, ci_lower, ci_upper, p_value, confidence_score, sample_treatment, sample_control, generated_at] is delivered And on-demand exports complete within 5 minutes; scheduled exports deliver at the configured time with ≥99% 7-day success rate; failures are retried up to 3 times and alert the owner
Auditability and Traceability of Attribution
Given a finance reviewer drills into an attribution result When requesting lineage for a theme-segment metric Then the system displays the underlying account-level joins and outcome events with timestamps and IDs, with filters and a downloadable sample (min 100 rows or full set, whichever is smaller) And every exported row includes source pointers (provider, event_id) and computation_id to reproduce the metric And all lineage views respect access controls and redact PII fields unless explicitly granted
Optimization Insights for Wording, Channel, and ETA
"As a product manager, I want data‑driven recommendations on wording, channel, and ETA so that our follow‑ups achieve higher engagement and satisfaction."
Description

Analyze variant performance across wording templates, channels, and ETAs to recommend the best combination per theme and segment. Support A/B testing and Bayesian bandit strategies, adjust for multiple comparisons, and present explainable drivers (e.g., "short subject + Slack within 2h" performs best for SMB high‑urgency themes). Provide prescriptive suggestions directly in the composer with one‑click apply, display sample sizes and significance, and prevent risky recommendations when data quality is insufficient.

Acceptance Criteria
Composer: One-Click Apply of Prescriptive Suggestions per Theme & Segment
Given a user is composing outreach for a selected theme T and segment S with at least one eligible recommendation When the composer loads Then the top recommended combination (wording template, channel, ETA) is displayed with expected uplift and a 95% interval And a Why this link is visible When the user clicks One-Click Apply Then subject/body template, channel, and ETA fields are populated within 1 second And an Undo action is available for 30 seconds And the action is logged with recommendation ID, model version, data window, and user ID
Insights: Ranked Best Combination with Explainable Drivers
Given the user views Optimization Insights for theme T and segment S with a selected time window When analysis completes Then a ranked list of top combinations is shown with expected uplift vs baseline and 95% interval for the selected objective And each item displays the drivers panel with the top 3 factors (e.g., subject length, channel, ETA bucket) and their signed contributions in plain language When the user selects a recommendation Then it becomes preselected in the composer suggestion panel
A/B Testing: Setup, Randomization, and Significance
Given the user creates an A/B test for theme T and segment S with K ≥ 2 variants and specified allocations When the test starts Then traffic is randomized according to allocation with ≤ 1% drift per variant over 1,000 assignments And per-variant metrics (opens, replies, acknowledgment time, sentiment shift, renewal impact) update at least every 5 minutes And the UI displays sample size per variant, baseline, effect size, and p-value for the selected objective Then variants with p-value < 0.05 are labeled Significant (unadjusted); otherwise Inconclusive (multiple-comparison adjustment is covered separately)
Bayesian Bandit: Adaptive Allocation and Reporting
Given the user enables Bayesian Bandit for theme T and segment S with K ≥ 2 variants When live traffic is received Then allocation updates no more frequently than every 10 minutes and maintains a ≥ 10% exploration floor per variant until 500 total assignments or 24 hours (whichever is later) And the UI shows each variant’s posterior probability of being best and 90% credible interval for the selected objective Then a winner is recommended when probability(best) ≥ 95% and each active variant has ≥ 100 assignments And the final recommended combination is synced to the composer suggestions
Statistical Correction: Multiple Comparisons Adjustment Across Variants
Given K ≥ 3 active variants across wording/channel/ETA When significance is calculated for the selected objective Then the UI displays both raw and adjusted significance (method name shown) and uses the adjusted result for Winner/Inconclusive labels And adjusted results match the selected method’s standard within numerical tolerance (e.g., BH for FDR 5% or Holm–Bonferroni for FWER 5%) And exporting or sharing preserves the adjusted values and method label
Data Quality Gate: Prevent Risky Recommendations
Given data quality thresholds are not met (e.g., per-variant n < 100 exposures for binary objectives or < 50 observations for time-based objectives, data freshness > 72h, or estimated power < 0.8) When generating recommendations Then the UI displays Insufficient data with unmet thresholds and estimated additional sample/time required And the One-Click Apply action is disabled with a tooltip explaining why When thresholds are later satisfied Then the recommendation automatically enables within 5 minutes without page reload
Objective Selection: Optimize by Metric and Segment
Given the user selects an optimization objective (reply rate, acknowledgment time, sentiment shift, or renewal impact) and filters by theme T and segment S When recommendations are generated Then suggested combinations optimize the selected objective and display expected change vs baseline for that objective with its interval and sample sizes And if a guardrail metric is breached (e.g., renewal impact decreases beyond configured threshold), the UI flags the trade-off and requires explicit confirmation before Apply And all displayed metrics, intervals, and counts reflect the selected objective and filters
Shareable Loop Impact Digests
"As an executive sponsor, I want a shareable digest that proves the value of closing the loop so that I can align stakeholders and secure ongoing investment."
Description

Generate scheduled and on‑demand digests that summarize opens, replies, acknowledgment time, sentiment shift, and renewal impact by theme and segment. Include narrative insights, trends, and recommended actions, with filters, branded share links with expiry, PDF/CSV export, and Slack/email distribution. Enforce viewer permissions and redaction rules, log shares for audit, and link out to underlying conversations and associated Jira/Linear tickets to demonstrate value and drive action.

Acceptance Criteria
Scheduled Digest Generation and Delivery
Given an org-defined schedule with timezone and recipient lists (Slack channels and email addresses) When the scheduled time occurs Then a digest is generated for the previous schedule window and delivered to all recipients within 5 minutes And duplicate digests are not sent for the same schedule window And each generation and delivery attempt is logged with timestamp, status, recipients, and duration When a schedule is paused or disabled Then no digests are generated or sent for that schedule When delivery to Slack or email fails Then the system retries up to 3 times with exponential backoff and alerts org admins within 10 minutes
On‑Demand Digest Generation with Filters
Given a permitted user selects filters (date range, theme(s), segment(s), channel(s), and owner) When the user clicks Generate Digest Then the digest renders within 60 seconds and reflects only the selected filters and time window And the applied filters are displayed in the digest header And the same inputs produce identical metrics within the session unless new source data arrives When invalid or conflicting filters are provided Then the user is shown a specific validation error and generation is blocked
Metrics and Breakdown Integrity
Given a generated digest Then it displays, per theme and per segment, the following metrics: opens, replies, average acknowledgment time (hours), sentiment shift (delta vs previous period), and renewal impact (count and %) And totals equal the sum of visible breakdowns within a rounding tolerance of ±1 unit And the comparison period is the immediately preceding period of equal length and is clearly labeled And metric tooltips disclose definitions and calculation methods
Narrative Insights, Trends, and Recommended Actions Presence
Given a generated digest Then it includes at least 3 narrative insights, each with: a plain-language summary (≥2 sentences), trend direction (up/down/flat), magnitude (% change), and confidence (low/medium/high) And it lists at least 3 recommended actions, each mapped to a theme with expected impact, suggested owner, and a one‑click Create Jira/Linear control that pre-fills context And insights and actions respect the selected filters and time window
Branded Share Links with Expiry and Revocation
Given a digest and an org with branding settings When a user creates a share link Then the link uses the org logo/colors and an org-specific subdomain And the creator can set an expiry (24h, 7d, or custom date/time), defaulting to 7d And expired links return HTTP 410 and are inaccessible When a share link is revoked by an admin or its creator Then subsequent requests return HTTP 403 And access to share links enforces viewer permissions and redaction rules; if public sharing is disabled, authentication is required And all link creation, access, revocation, and expiry events are logged with actor, timestamp, and IP
PDF and CSV Export Fidelity
Given a generated digest When PDF export is requested Then the PDF matches on-screen content (branding, charts, narrative, metrics) and generates within 30 seconds When CSV export is requested Then it contains one row per theme×segment with columns for all metrics, filters, and time window, and downloads within 15 seconds And exported files respect filters, permissions, and redaction rules And filenames follow {org}-{digest-type}-{yyyy-mm-dd}-{filters}.{pdf|csv}
Viewer Permissions, Redaction, Audit Logging, and Deep Links
Given a viewer accesses a digest (UI, share link, or export) Then they only see themes/segments and message excerpts permitted by their role, with PII (emails, phone numbers, names in bodies) redacted per org rules And every view, export, and share is written to an immutable audit log with user/link id, action, timestamp, IP, and outcome, visible to org admins And each metric item includes deep links to underlying conversations and associated Jira/Linear tickets; access is enforced per system permissions, showing a redacted preview and request-access control if denied

Weight Profiles

Save and apply named weighting presets (e.g., Enterprise-ARR, Self-Serve, New-User) that factor revenue, plan, and lifecycle stage. Quickly switch or schedule profiles by sprint or initiative so the ranked queue always reflects current goals—making prioritization fast, consistent, and defensible.

Requirements

Weight Profile Builder
"As a product manager, I want to define and save named weighting profiles so that our prioritization reflects current strategy without manually recalculating scores."
Description

Provide a first-class UI and backend to create, edit, clone, and delete named weighting profiles that combine factors such as revenue (ARR), plan tier, and lifecycle stage. Enforce normalization and guardrails (e.g., weights must total 100%, valid ranges, required fields) with inline validation and error messaging. Include profile metadata (name, description, tags, owner), versioning with change notes, default profile designation per workspace, and a template library (e.g., Enterprise-ARR, Self-Serve, New-User). Persist profiles at the workspace level with multi-tenant isolation and support localization of labels. Ensure changes are immediately available to the scoring engine and safely roll back to previous versions if needed.

Acceptance Criteria
Create new profile with required fields and normalized factor weights
Given a workspace user with permission to manage weighting profiles, When they open the builder and enter Name, Description, Owner, Tags, and factor weights (ARR, Plan Tier, Lifecycle Stage), Then the Save action is disabled until all required fields are valid. Given factor weights accept integers from 0 to 100 and the total must equal 100, When the sum != 100 or any weight is out of range, Then inline errors display per field, a form-level error banner appears, and Save is disabled. Given all fields are valid and weights total 100, When the user saves, Then a new profile is created with version 1.0, timestamped, and persisted at the workspace level. Given creation succeeds, When the profiles list is refreshed, Then the new profile appears and is selectable in ranking settings.
Edit existing profile with versioning and scoring engine propagation
Given an existing profile, When a user modifies factor weights or metadata and attempts to save, Then a Change Notes field is required and must be non-empty. Given the edit is saved, Then the profile version number increments by one minor version (e.g., 1.0 -> 1.1), the change notes are stored, and an audit record is created with editor, timestamp, and diff of changes. Given the edit is saved, Then the scoring engine receives the updated weights and all affected queues re-rank within 5 seconds. Given propagation completes, When the user views the live theme map or ranked queue, Then scores reflect the updated weights.
Clone an existing profile into a new named profile
Given a user selects Clone on a profile, When the clone dialog opens, Then all factor weights and metadata (except version) are pre-populated. Given the clone form opens, Then Name is pre-filled as "Copy of <Original Name>" and must be unique; Owner defaults to the current user; Default flag is unchecked. Given the user saves the clone, Then a new profile is created with version 1.0, retaining the cloned weights and metadata, and appears in the profiles list without changing the current default.
Default profile management and delete safeguards
Given a workspace, When a profile is set as Default, Then any previously default profile is automatically unset so that exactly one default exists at all times. Given a profile is set as Default, When the default is changed to another profile, Then all queues using the default profile re-rank within 5 seconds. Given a profile is the current Default, When a user attempts to delete it, Then the system blocks deletion and prompts to assign a new default first. Given a non-default profile is deleted and the user confirms, Then the profile is removed from the workspace, disappears from lists, and is no longer selectable in settings.
Create profile from template with localized labels
Given the template library is available, When a user opens the New from Template menu, Then Enterprise-ARR, Self-Serve, and New-User templates are displayed. Given a template is selected, Then factor weights, descriptions, and recommended tags pre-populate the builder and remain editable before save. Given the workspace locale is set (e.g., en, es, de), When the builder and validation messages render, Then all field labels, help text, and errors display in the selected locale. Given the user saves a template-based profile, Then the profile is created with a Template tag referencing the source template and version 1.0.
Workspace-level persistence and multi-tenant isolation
Given two distinct workspaces A and B, When a user in workspace A lists profiles, Then only profiles belonging to workspace A are returned. Given a profile ID from workspace B, When a user in workspace A attempts to access it via UI or API, Then the request is rejected with 404/Forbidden and no profile data is leaked. Given profiles are created/edited in a workspace, Then Owner, Tags, and audit history are stored and retrievable only within that workspace context.
Rollback profile to a previous version with safe propagation
Given a profile with versions (e.g., 1.0, 1.1, 1.2), When a user selects Rollback to 1.0 and provides change notes, Then a new version 1.3 is created with content identical to 1.0 and the rollback notes recorded in the audit trail. Given a rollback is executed, Then no historical versions are deleted; version history remains intact and viewable. Given the rollback is saved, Then the scoring engine applies the rolled-back weights and affected queues re-rank within 5 seconds with no errors. Given the profile is Default, When rollback occurs, Then the system applies changes without requiring downtime or manual reassignment.
Attribute Enrichment & Mapping
"As a CX lead, I want feedback items to be accurately linked to revenue, plan, and lifecycle data so that weighted prioritization is trustworthy and defensible."
Description

Implement a reliable data mapping layer that enriches every feedback item with required attributes (revenue/ARR, plan tier, lifecycle stage) sourced from CRM/billing and user databases. Provide identity resolution (account/user matching), configurable mapping rules, and fallbacks for unknown or missing attributes. Support backfilling historical items, track attribute freshness and coverage, and expose confidence on mapped fields. Include monitoring, alerts, and dashboards for data completeness and latency to ensure weighting calculations remain accurate.

Acceptance Criteria
Real-time enrichment on ingestion
Given a new feedback item is received with identifiable user/account fields via any supported channel, When the item is ingested, Then revenue/ARR, plan tier, and lifecycle stage are enriched and attached within P95 60s and P99 5m. Given enrichment completes, When viewing the item via API or UI, Then each mapped attribute includes value, source system, source record ID, last_refreshed_at timestamp, and confidence (0.0–1.0). Given the same item is re-ingested or replayed, When enrichment runs, Then the operation is idempotent and does not create duplicate attribute records. Given a transient source outage occurs, When enrichment attempts fail, Then retries use exponential backoff for up to 24h while preserving ingestion order.
Deterministic identity resolution
Given inputs include combinations of account_id, external_id, user_id, and email, When multiple potential matches exist, Then matching follows precedence: exact account_id > exact external_id > exact user_id > normalized email > email-domain-to-account mapping. Given a match is selected, When the computed identity confidence is below 0.80, Then the item is tagged ambiguous_identity, enrichment uses fallbacks, and the confidence is recorded on all mapped fields. Given no match is found, When enrichment completes, Then the item is tagged unmatched, confidence=0.0 for identity-derived fields, and the retry policy is applied. Given normalized email rules (case-folding, trimming, alias removal) are applied, When matching runs, Then results are deterministic and repeatable across runs.
Configurable mapping rules and precedence
Given an admin drafts mapping rules for ARR, plan tier, and lifecycle stage, When rules are validated, Then the system rejects configurations with missing sources, circular references, or unsafe transforms and returns clear error messages. Given precedence across CRM, billing, and user DB is configured, When sources disagree, Then the precedence determines the final value and the selected source is recorded in lineage metadata. Given a new rule version is published, When activated, Then all new ingestions use the new version and the prior version remains immutable and auditable. Given reprocessing is enabled for a rule change, When executed, Then existing items are re-evaluated, updated if values change, and a change history is persisted per attribute.
Fallbacks and default handling
Given a required attribute is missing from all sources, When enrichment runs, Then a configured default value is applied with confidence<=0.20 and an is_fallback flag is set on the attribute. Given a source is temporarily unavailable, When enrichment attempts exceed retry limits (24h), Then the item is flagged stale_enrichment, fallbacks are applied, and the event is logged for monitoring. Given fallbacks are present on an item, When weight profiles consume attributes, Then prioritization and scoring complete without errors and a fallback_used indicator is included in the audit trail. Given a rolling 24h window, When measuring attribute coverage, Then the unknown/fallback rate for required attributes is <=2% or a coverage alert is emitted.
Historical backfill processing
Given an admin selects a date range and segment, When backfill is started, Then historical items are enriched and missing attributes are filled without duplicating existing values. Given a backfill is in progress, When querying job status via API or UI, Then progress (% complete), throughput, estimated time remaining, and current time window are visible. Given a backfill job is interrupted, When it resumes, Then processing continues from the last checkpoint without reprocessing completed items. Given up to 1,000,000 items under normal load, When backfill executes, Then it completes within 24 hours and respects external source rate limits with no more than 1% error rate.
Freshness, coverage, and confidence exposure
Given any enriched attribute on a feedback item, When the item is fetched via API or UI, Then last_refreshed_at, freshness_age, confidence, and source are exposed per attribute. Given a 24h freshness SLA for required attributes, When measuring over a rolling 24h period, Then P95 freshness_age is <=24h and P99 is <=48h. Given hourly coverage calculations, When metrics are computed, Then coverage for required attributes is >=98% overall and segmented by channel and customer tier. Given confidence scoring rules, When an attribute is inferred or fallback-derived, Then confidence<0.50; when an attribute is direct exact-source matched, Then confidence>=0.90.
Monitoring, dashboards, and alerts
Given operational metrics (enrichment latency, freshness, coverage, error rate, queue depth), When viewing dashboards, Then current values, 7-day trends, and SLO targets are visible with filters for source and channel. Given alert thresholds (coverage<98%, P95 latency>60s sustained for 5m, error rate>1%), When a threshold is breached, Then an alert is sent to configured channels within 5 minutes including context and a runbook link. Given remediation actions are taken, When metrics return within SLO, Then alerts auto-resolve and incidents show resolved status with resolution time. Given data source authentication failures occur, When detected, Then a high-severity alert is emitted naming the failing source and the affected percentage of items.
Real-time Profile Application
"As a PM, I want the ranked queue and theme map to update immediately when I switch profiles so that decisions always reflect the latest goals."
Description

Integrate the active profile into the scoring pipeline to reweight cluster/theme scores and recompute the ranked queue and live theme map in near real time when a profile is activated or changed. Provide atomic activation with immediate UI feedback, caching for performance, and incremental recomputation to avoid full reprocessing. Persist the active profile at the workspace level with per-user preview capability and a safe fallback to the default profile on errors. Log activation events for traceability.

Acceptance Criteria
Atomic Activation Updates Queue and Theme Map
Given a workspace with an existing active profile and open ranked queue and live theme map When a user with permission activates profile "Enterprise-ARR" Then the UI shows "Active: Enterprise-ARR" within 300 ms And the ranked queue reflects new weights within 2 seconds And the live theme map re-renders with updated scores within 5 seconds And no mixed-state is displayed (items show either old or new scores only) And the /active-profile API returns "Enterprise-ARR" for subsequent requests within 1 second
Incremental Recompute on Small Weight Changes
Given the active profile’s weight multipliers are adjusted by <10% per factor When the user saves and activates the adjusted profile Then the scoring service executes in incremental mode (flag incremental=true in telemetry) And recomputed_nodes_count / total_nodes <= 0.30 And end-to-end recomputation latency <= 1.5 seconds for a 10k-item staging dataset And no request or worker errors are logged during recomputation
Caching Speeds Re-Activation of Recent Profile
Given profile "Self-Serve" was active within the last 15 minutes (cache TTL) When a user re-activates "Self-Serve" Then cached intermediates are used (cache_hit=true in logs) And recomputation completes in <= 1.0 second for the same dataset And if activation occurs after TTL, cache_miss=true and cold recompute proceeds And the resulting queue and theme map match a cold recompute output bit-for-bit
Workspace-Level Active Profile Persists Across Sessions
Given profile "New-User" is activated for a workspace When the initiator refreshes, logs out/in, or opens the app on another device and another member opens the workspace Then all clients show "Active: New-User" consistently And new sessions read the active profile within 2 seconds of activation And the active profile persists across browser restarts and devices And per-user preview states do not alter the stored workspace active profile
Per-User Profile Preview Without Changing Workspace Active
Given the workspace active profile is "Enterprise-ARR" When a user starts preview of profile "Self-Serve" Then only that user’s UI shows "Preview: Self-Serve" and computes queue/map using "Self-Serve" And other users continue to see "Active: Enterprise-ARR" with no changes And exiting preview reverts the UI and computations within 1 second And no activation event is recorded (only a preview event with actor_id) And the workspace active profile remains unchanged in storage
Safe Fallback to Default on Activation Errors
Given a simulated scoring pipeline failure or invalid profile configuration When a user attempts to activate profile "Q" Then the system falls back to the default profile within 1 second And a non-blocking banner explains the fallback with a retry option And the ranked queue and theme map remain available and consistent And an error and fallback event are logged with correlation_id and cause And the failed profile does not persist as the workspace active profile
Activation Event Logging and Auditability
Given audit logging is enabled When any profile activation succeeds Then an audit event is recorded within 2 seconds containing profile_id_before, profile_id_after, actor_id, workspace_id, timestamp_utc, weights_hash_before, weights_hash_after, result=success, latency_ms And the event is immutable, ordered, and visible in the audit log UI/API And the event is searchable by actor_id, workspace_id, and time range via API And when an activation fails, a failure event is recorded with result=failure and fallback=true (if applied)
Scheduling & Sprint Binding
"As a PM, I want to schedule which weighting profile is active for each sprint or initiative so that prioritization automatically aligns with our delivery plan."
Description

Enable users to schedule profile activations by date/time windows and bind profiles to sprints or initiatives. Integrate with Jira/Linear to detect current/next sprint dates and initiative tags, resolve conflicts (e.g., overlapping schedules) with clear precedence rules, and handle time zones. Provide calendar views, notifications before/after activation, and a fallback to the default profile if a schedule fails. Record all scheduled activations in the audit trail.

Acceptance Criteria
Date/Time Window Activation with Time Zone Support
Given a workspace time zone is set and a Weight Profile named "Enterprise-ARR" exists And a schedule is created to activate "Enterprise-ARR" from a specified local start to end time When the schedule is saved Then the system persists start/end in UTC and displays them in the calendar using the workspace time zone And at the start boundary the active profile switches to "Enterprise-ARR" within 60 seconds And at the end boundary the system re-evaluates bindings (schedule/sprint/initiative/default) and switches within 60 seconds to the next applicable profile And schedules spanning DST changes fire at the intended local wall-clock times
Sprint-Bound Activation via Jira/Linear
Given Jira or Linear is connected and the workspace has mapped boards/teams with current and next sprint windows And a Weight Profile named "Sprint-Focus" is bound to the current sprint of a selected board/team When the current time enters the detected sprint window Then the active profile switches to "Sprint-Focus" within 60 seconds and remains active until the sprint ends And on sprint rollover the system evaluates the next sprint binding within 60 seconds And if sprint dates cannot be resolved at evaluation time, the system retains the current profile, logs an error in the audit trail, and surfaces a warning to the scheduler
Initiative Tag Binding and Application
Given Jira/Linear initiatives (or epics/projects) expose start and end dates or active states And a Weight Profile named "New-User" is bound to initiative "Onboarding Revamp" When the current time falls within the initiative's active window (or the initiative is marked Active) Then the active profile switches to "New-User" within 60 seconds And when the initiative window ends (or is marked Inactive/Done) the system re-evaluates and applies the next applicable profile within 60 seconds And initiative time zone metadata is respected and converted to the workspace time zone for evaluation and display
Conflict Resolution and Precedence Rules
Given multiple potential activations overlap in time When determining the active profile Then precedence is applied in this order: Explicit Date/Time Schedule > Sprint Binding > Initiative Binding > Default Profile And for overlapping explicit schedules, the schedule with the most recent Last Updated timestamp takes precedence; if tied, the one with the nearest end time wins And for multiple applicable sprint bindings, the sprint with the nearest end date takes precedence; if tied, the board/team key in ascending order is used as a deterministic tie-breaker And for multiple applicable initiative bindings, the initiative with the nearest end date takes precedence; if tied, the most recent Last Updated wins And each resolution decision is recorded in the audit trail with the rule applied
Activation Notifications (Pre and Post)
Given a schedule or binding will activate a profile When the activation is T-minus 15 minutes Then a pre-activation notification is sent to the scheduler and workspace admins via in-app and email (if enabled) including profile, trigger, window, and time zone And when the activation occurs Then a post-activation notification is sent within 1 minute including old/new profiles and a link to the audit record And if an activation fails, a failure notification with the error reason and fallback state is sent immediately
Calendar View with Schedules and Bindings
Given the user opens the Scheduling calendar When viewing Month/Week Then all explicit schedules are displayed as blocks in the workspace time zone with clear start/end times And sprint and initiative windows are overlaid with distinct labels and colors And overlaps are visually indicated and a tooltip explains the active profile per the precedence rules And clicking any block opens details with profile, trigger type, time window, and links to edit or audit record And the calendar reflects backend changes within 60 seconds
Audit Trail for Scheduled Activations and Outcomes
Given any scheduled, sprint-bound, or initiative-bound activation is evaluated When an activation succeeds, fails, or is skipped due to precedence Then an audit trail entry is recorded within 5 seconds capturing: timestamp (UTC), workspace time zone, actor (user/system), trigger type (schedule/sprint/initiative), source system (Jira/Linear/EchoLens), old profile, new profile, outcome (Success/Failure/Skipped), and reason/message And audit entries are immutable, filterable by date/profile/trigger/outcome, and viewable in the Audit Log UI And each notification sent links to the corresponding audit entry
Profile Preview & Impact Diff
"As a PM, I want to preview how a profile will change rankings so that I can validate its impact before making it active."
Description

Offer a sandbox preview that applies a selected profile without committing it, showing a before/after comparison of the ranked queue and theme map. Highlight items and themes that move most, summarize changes (e.g., rank deltas, affected top-N), and allow exporting a snapshot for stakeholder review. Include controls to adjust weights and instantly see projected impact before publishing.

Acceptance Criteria
Preview Without Commit
- Given a user with edit permissions selects a saved weight profile, When they click “Preview” to open the sandbox, Then the active production profile and ranked queue remain unchanged until “Publish” is confirmed. - Given Preview mode is active, When the user exits Preview, Then all temporary changes are discarded and the pre-preview state is restored. - Given Preview mode is active, Then the UI clearly displays a “Preview” badge with the selected profile name and an “Exit Preview” control. - Given a preview session occurs, Then an audit entry is recorded as a non-persistent preview event (user, timestamp, profile) and no profile-change audit event is created unless “Publish” is used.
Ranked Queue Diff Highlighting
- Given baseline and preview queues are computed, When Preview mode is active, Then each item displays its rank delta with directional arrows and a signed integer (e.g., ▲+5, ▼-2, =0). - Given Preview mode is active, When an item’s rank delta magnitude ≥ 3 (default threshold), Then the item row is highlighted; When the threshold is changed, Then highlighting updates immediately. - Given Preview mode is active, When the user selects “Sort by Movement,” Then the queue sorts by absolute rank delta (desc) with stable secondary sort by preview rank. - Given an item enters or exits the top-N list under preview, Then it is badged respectively as “Entered Top N” or “Dropped from Top N,” where N is user-configurable (default 20).
Theme Map Change Visualization
- Given Preview mode is active, When the theme map renders, Then each theme shows baseline vs preview impact via dual-value tooltip (baseline, preview, % change) and visual diff styling (color/outline). - Given the user toggles “Show only changed themes,” Then only themes with |% change| ≥ 5% are visible. - Given a dataset up to 10k feedback items and 500 themes, Then the initial preview diff render completes within 2 seconds and subsequent interactions (pan/zoom/toggle) respond within 300 ms at the 95th percentile. - Given accessibility requirements, Then all diff encodings have non-color cues and tooltips are keyboard accessible (tab/shift+tab, enter).
Top-N Impact Summary
- Given Preview mode is active, Then a summary header displays: count entering top N, count leaving top N, and median/mean rank delta for top N (N default 20, user-adjustable). - Given the summary is visible, When the user expands “Biggest Movers,” Then the top 5 positive and top 5 negative movers are listed with links to jump to each item. - Given N is changed, Then the summary recalculates within 300 ms and the list updates accordingly. - Given there are ties at the cutoff, Then tie-breaking follows preview rank then item ID to ensure determinism.
Adjust Weights In-Preview
- Given Preview mode is active, When the user modifies revenue/plan/lifecycle weights via sliders or inputs, Then the queue, deltas, theme map, and summary update within 300 ms of the final input (debounced). - Given weight inputs have constraints, Then values are validated (range 0–10, numeric) and invalid entries show inline error messaging without applying changes. - Given the user clicks “Reset to Profile Defaults,” Then all weights revert to the selected profile’s saved values and diffs recalculate. - Given the user unsavedly changes weights, Then exiting Preview prompts to discard or continue; choosing discard restores pre-preview state.
Export Snapshot for Stakeholders
- Given Preview mode is active, When the user clicks “Export Snapshot,” Then a point-in-time report is generated capturing: selected profile, weight values, timestamp, filters, top-N summary, queue with ranks and deltas, and theme map summary. - Given export options are shown, Then the user can export: CSV (queue with baseline rank, preview rank, delta) and PDF (multi-section report with charts); both complete within 10 seconds at the 95th percentile. - Given a snapshot is exported, Then its content is deterministic for the chosen timestamp and includes a unique ID for audit traceability. - Given permissions are enforced, Then viewers without edit rights can open the PDF/CSV but cannot publish changes from the snapshot.
Permissions & Audit Logging
"As a workspace admin, I want controlled access and a full audit trail for profile changes so that we meet governance and compliance requirements."
Description

Add role-based access controls to restrict who can create, edit, approve, and activate profiles (e.g., Admin, Editor, Viewer). Implement optional approval workflow for activation in governed workspaces. Maintain a tamper-evident audit log of all profile changes and activations (who, what, when, previous vs. new values) with the ability to view history and roll back to prior versions. Support exporting logs for compliance.

Acceptance Criteria
Role Permissions on Weight Profiles
Given a Viewer is authenticated, When they access the Weight Profiles UI or APIs, Then they can read profile lists and history but cannot create, edit, approve, activate, or roll back and forbidden actions return HTTP 403. Given an Editor is authenticated and the workspace approval workflow is disabled, When they create or edit a profile, Then the action succeeds and is persisted; And When they attempt to activate a profile, Then activation succeeds. Given an Editor is authenticated and the workspace approval workflow is enabled, When they attempt to activate a profile, Then they are required to submit for approval and cannot directly activate (HTTP 403 on direct activation API). Given an Admin is authenticated, When they create, edit, approve, activate, or roll back a profile, Then the action is permitted. Given a user is unauthenticated, When they call any profile management API, Then the response is HTTP 401.
Activation Approval Gate in Governed Workspaces
Given workspace setting "Require activation approval" is enabled, When a user submits an activation request, Then the profile state changes to Pending Approval and a request record is created with requester, timestamp, and requested changes. Given a Pending Approval exists, When an Admin (not the requester) approves it, Then the profile is activated and the approver, timestamp, and decision comment are recorded; And When rejected, Then the state returns to Draft with rejection reason recorded. Given approval is required, When any non-Admin attempts to approve or reject, Then the action is blocked with HTTP 403. Given approval is disabled, When a user with activation permission activates a profile, Then it activates immediately with no Pending Approval state. Given a Pending Approval older than the configured expiry (e.g., 14 days), When it is viewed, Then it is shown as Expired and cannot be approved; a new request must be submitted.
Tamper-Evident Audit Log for Profile Changes and Activations
Given any profile create, update, submit-for-approval, approve, reject, activate, deactivate, rollback, or schedule-change action occurs, When the action completes, Then an audit event is written containing event_id, timestamp (UTC ISO 8601 ms), actor_id, actor_email, actor_role, action_type, object_type, object_id, previous_values, new_values, request_id, ip_address/user_agent (if available). Given an audit event is written, When stored, Then it includes a content_hash = SHA-256(payload) and a previous_hash referencing the prior event for the same object, enabling a verifiable hash chain. Given the audit store is queried for integrity, When any event is tampered with, Then recomputing the chain detects a mismatch and the system flags the chain as invalid. Given an action fails (4xx/5xx), When a partial change is rolled back, Then a failure audit event is still recorded with error_code and no state mutation in new_values. Given timekeeping, When events are created, Then timestamps are monotonic per object_id and not in the future relative to server time.
Audit Log History View, Filter, and Pagination
Given a user with view permission opens a profile’s history, When the history loads, Then the last 50 events are shown in reverse chronological order within 2 seconds for up to 10k total events. Given filters are applied (date range, action type, actor), When the user applies them, Then only matching events are returned and counts update accordingly. Given pagination controls, When the user requests the next page, Then the next 50 events are returned using a stable cursor and no duplicates across pages. Given a workspace-wide audit view, When a user searches by profile name/id or request_id, Then matching events are returned within 3 seconds for up to 100k events. Given an event is selected, When the user expands it, Then previous_values vs new_values are displayed as a field-level diff with red/green highlighting and exact old/new values.
Rollback Profile to Prior Version from History
Given an Admin or Editor with rollback permission views a profile’s history, When they select a past version and choose Roll Back, Then the system creates a new version equal to the selected version’s values and records a rollback audit event with source_version_id and target_version_id. Given approval workflow is enabled, When "Activate after rollback" is selected, Then an activation request is submitted for approval; direct activation is blocked for non-Admins. Given a rollback completes, When the current active profile should remain unchanged, Then the rolled-back version is saved as Draft unless explicitly activated. Given idempotency, When the same rollback to the same source version is retried after a transient failure, Then at most one new version is created. Given constraints, When fields present in the historical version are deprecated, Then rollback validates and lists incompatible fields and aborts with a clear error without mutating the profile.
Export Audit Logs to CSV/JSON with Time Range
Given a user with export permission opens the export dialog, When they select CSV or JSON and a time range, Then the system estimates row count and starts an export job on confirm. Given an export exceeds 100k rows, When the job runs, Then the export is processed asynchronously, compressed (.zip), and a secure download link is emailed and available in the UI within 15 minutes for up to 5 million rows. Given an export completes, When the file is downloaded, Then it contains a header row/fields (for CSV) or an array of objects (for JSON) with all audit fields including content_hash and previous_hash. Given security requirements, When a download link is generated, Then it is single-use, requires authentication, and expires after 7 days or after one successful download, whichever comes first. Given filters (actor, action, profile id), When applied in the export dialog, Then only matching events are included in the export.
Access Control for Audit Log Visibility
Given role-based access, When a Viewer accesses audit logs, Then they can view profile-scoped history but cannot export or delete logs; export controls are hidden and export APIs return HTTP 403. Given an Editor accesses audit logs, When viewing, Then they can view profile-scoped and workspace-scoped logs; When exporting, Then the action is blocked unless explicitly granted export permission (HTTP 403 by default). Given an Admin accesses audit logs, When viewing or exporting, Then both actions are permitted; When changing log retention settings (if available), Then only Admin can modify and changes are audited. Given governed workspaces, When a user is outside the workspace (cross-tenant), Then no logs are visible and APIs return HTTP 404/403 as configured for tenant isolation. Given PII constraints, When any user without PII-view permission views logs, Then sensitive fields (emails in values, IPs) are masked in UI and exports.
Profile API & Sync Metadata
"As a developer, I want API access and profile metadata in downstream syncs so that our tooling can automate and audit prioritization context."
Description

Expose secure REST endpoints to manage profiles (CRUD, activate, schedule) and emit webhooks for activation events. Include active profile metadata (ID, name, version) in one-click Jira/Linear sync payloads and exports so downstream systems can trace prioritization context. Provide import/export of profiles as JSON, API tokens with scopes, and rate limiting. Ensure parity between UI and API capabilities.

Acceptance Criteria
Profile CRUD via REST
Given a valid token with scope "profiles:write", When POST /profiles with valid JSON including name and weight rules, Then response is 201 Created with Location header and body containing id (UUID), name, version=1, createdAt, updatedAt. Given missing required fields or invalid weights, When POST /profiles, Then response is 400 with code=VALIDATION_ERROR and field-level messages. Given an existing profile, When GET /profiles/{id}, Then response is 200 with full profile JSON including version and status (active|inactive). Given an existing profile, When PUT /profiles/{id} with If-Match=<version>, Then response is 200 and version increments by 1; When If-Match is missing or stale, Then response is 412 Precondition Failed. Given a non-active, non-scheduled profile, When DELETE /profiles/{id}, Then response is 204 and subsequent GET returns 404; When profile is active or scheduled, Then response is 409 Conflict.
Immediate and Scheduled Activation via API
Given a profile exists and caller has scope "profiles:activate", When POST /profiles/{id}/activate with {"mode":"now"}, Then profile becomes active immediately, response 200 with active=true and activatedAt (UTC). Given a profile exists, When POST /profiles/{id}/activate with {"mode":"schedule","startAt":"2025-09-01T10:00:00Z","endAt":"2025-09-07T10:00:00Z"}, Then response 200 with scheduleId, and profile auto-activates at startAt and auto-deactivates at endAt. Given overlapping schedules are submitted for the same profile, Then response is 409 Conflict with code=OVERLAPPING_SCHEDULE. Given a scheduled activation exists, When DELETE /profiles/{id}/schedules/{scheduleId}, Then response is 204 and schedule is canceled. Given system time reaches startAt, When calling GET /profiles/active, Then it returns the scheduled profile with correct id, name, version.
Activation Webhooks Delivery and Security
Given any profile is activated (immediate or scheduled), When the event occurs, Then a webhook POST is sent within 5 seconds to each configured endpoint with payload containing eventId (UUID), eventType="profile.activated", profileId, name, version, activatedAt, actorType, actorId. Given the webhook is delivered, Then the request includes headers X-Echolens-Signature (HMAC-SHA256) and X-Echolens-Timestamp; receivers verifying with the shared secret can validate the payload signature. Given the endpoint responds non-2xx, Then delivery is retried with exponential backoff for at least 24 hours; retries are idempotent and include the same eventId; after max retries, status is set to "failed" and visible via GET /webhooks/deliveries. Given multiple activation events, Then deliveries to a single endpoint preserve event order.
Active Profile Metadata in Jira/Linear Sync and Exports
Given a user triggers one-click Jira sync for a ranked item while a profile is active, Then the outbound payload includes activeProfile object with id, name, version, and activatedAt matching the system's current active profile at dispatch time. Given a user triggers one-click Linear sync, Then the payload includes the same activeProfile fields as Jira with identical values. Given a CSV or JSON export of the ranked queue is requested, Then the export includes fields activeProfileId, activeProfileName, activeProfileVersion, activatedAt for every row/item. Given no profile is active, When a sync or export is performed, Then the fields are present with null values and an additional context field prioritizationProfile="none". Given downstream systems log received payloads, Then the metadata values match those from GET /profiles/{id}.
Profiles Import/Export as JSON
Given an existing profile, When GET /profiles/{id}/export is called, Then response is 200 with Content-Type application/json and JSON conforms to schema version "echolens.profile.v1". Given a valid export JSON, When POST /profiles/import with overwrite=false, Then a new profile is created with a new id and version=1; response 201 with new id. Given the JSON contains an id matching an existing profile and overwrite=false, Then response is 409 Conflict with code=PROFILE_EXISTS. Given the JSON contains an id matching an existing profile and overwrite=true, Then the profile is updated and version increments; response 200. Given malformed JSON or schema violations, Then response is 400 with code=INVALID_PROFILE_SCHEMA and a list of offending paths.
API Authentication, Scopes, and Rate Limiting
Given an API token with scopes ["profiles:read"], When calling GET /profiles, Then response is 200; When calling POST /profiles, Then response is 403 Forbidden with code=INSUFFICIENT_SCOPE. Given token creation, When a token is generated, Then the secret is shown once and stored hashed; tokens can be revoked via DELETE /tokens/{id} and become unusable within 60 seconds. Given any authenticated request, Then responses include rate limit headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Given a client exceeds 1000 requests per minute per token, Then subsequent requests return 429 Too Many Requests with Retry-After header; the rate window resets per X-RateLimit-Reset. Given webhook deliveries, Then they are not counted against API rate limits.
UI and API Capability Parity
Given any action available in the UI for weight profiles (create, read, update, delete, activate, schedule, import, export), When queried via the API, Then an equivalent endpoint exists enabling the same action with the same validation rules and side effects. Given any capability available via the API for weight profiles, When accessed in the UI, Then the UI exposes that capability or provides a clear message if disabled by role or scope. Given a new profile-related capability is introduced, Then both UI and API reference documentation are updated concurrently with matching examples and field names. Given end-to-end tests are executed for UI-initiated and API-initiated flows, Then resulting profile states and audit logs are identical.

Renewal Lens

Automatically boosts themes tied to accounts near renewal or flagged at risk, blending ARR, health score, and renewal date into the score. Surfaces fixes that protect revenue and churn-prone segments so PMs can act before renewals are jeopardized.

Requirements

Account Revenue & Renewal Data Connectors
"As a CX lead, I want EchoLens to ingest ARR and renewal data from our CRM and billing tools so that theme scoring can reflect revenue impact and renewal timelines."
Description

Implement secure connectors to ingest ARR, renewal dates, contract terms, and account identifiers from CRM/billing systems (e.g., Salesforce, HubSpot, Stripe, Zuora, Chargebee) and customer success platforms (e.g., Gainsight, Catalyst). Normalize data, de-duplicate accounts, handle sandbox/production environments, and map external account IDs to EchoLens account entities used by feedback sources. Support scheduled syncs and near-real-time webhooks, field mapping, and data validation with error reporting and retry logic. Ensure encryption in transit/at rest, OAuth-based authentication, and permissions scoping so only necessary fields are accessed.

Acceptance Criteria
Connector Security, OAuth, and Least-Privilege Access Controls
Given an admin initiates connector setup for a supported system, When OAuth consent is requested, Then only the documented minimum scopes are requested and attempts to grant additional scopes are rejected with a clear error. Given a successful OAuth handshake, When tokens and refresh tokens are stored, Then they are encrypted at rest using KMS-managed AES-256 and redacted in all UI and logs. Given the connector is configured, When data is transmitted, Then TLS 1.2+ is enforced end-to-end. Given a user without Admin role, When they attempt to create, edit, or delete a connector, Then access is denied. Given an admin rotates credentials or revokes access in the source system, When EchoLens next calls the API, Then the connector detects revocation within 15 minutes and surfaces a reconnect prompt with error details. Given any connector action (create/update/delete/scope change), When it occurs, Then an immutable audit log entry is recorded with actor, timestamp, environment, and change set and is retrievable for 12 months.
Field Mapping for Revenue & Renewal Attributes
Given a connected system, When the admin opens Field Mapping, Then the UI lists available source fields and allows mapping to internal fields: arr_amount, arr_currency, renewal_date, contract_term, external_account_id, health_score (optional). Given required target fields (arr_amount, renewal_date, external_account_id), When they are not mapped, Then the configuration cannot be saved and inline errors identify missing mappings. Given mappings are set, When the admin clicks Test Mapping, Then a preview of at least 20 records shows parsed values and date formats normalized to ISO 8601. Given multiple connectors provide the same attribute, When a priority order is defined, Then the higher-priority source wins and the lower-priority value is retained as provenance but not used for scoring.
Environment Separation (Sandbox vs Production)
Given a source system with sandbox and production, When two connections are created, Then data, schedules, audit logs, and credentials are isolated per environment. Given a sandbox connection, When data is ingested, Then it is clearly labeled as sandbox and is excluded from Renewal Lens scoring by default unless an admin explicitly enables "Include Sandbox in Scoring". Given a sandbox connector is deleted, When deletion is confirmed, Then all sandbox-ingested records are purged within 15 minutes and are no longer queryable.
Account Identity Resolution and De-duplication
Given feedback sources reference EchoLens account entities, When external data is ingested, Then external_account_id is mapped to the correct EchoLens account using a configurable identity map and alias rules. Given multiple external records refer to the same account, When de-duplication runs, Then a single canonical account record is produced with a stable echo_account_id and merged attributes following a deterministic precedence policy. Given a conflicting attribute (e.g., different ARR) from two sources, When conflicts are detected, Then the lower-priority value is stored as alternate with provenance and the conflict is flagged in a reconciliation report accessible in the UI. Given identity mappings are corrected by an admin, When a reprocess job is triggered, Then affected records are re-linked without data loss and without creating duplicate accounts.
Incremental Scheduled Syncs and Near-Real-Time Webhooks
Given a connector is configured, When an initial sync is started, Then the system performs a full backfill supporting at least 100k accounts with paginated requests and idempotent upserts. Given the initial sync completes, When scheduled syncs run, Then an incremental strategy using an updated_at watermark or change data capture is used to fetch only changed records. Given the source emits webhooks, When relevant account fields change, Then EchoLens processes the webhook and updates the account within 5 minutes at the 95th percentile. Given transient API errors or rate limits, When requests fail, Then retries are attempted up to 5 times with exponential backoff and jitter, respecting vendor rate limit headers. Given duplicate webhook deliveries, When processing occurs, Then idempotency ensures no duplicate records or side effects are produced.
Data Validation, Error Handling, and Observability
Given data is ingested, When ARR is parsed, Then values must be numeric and >= 0; currency must be a valid ISO 4217 code; invalid records are rejected with reasons. Given renewal_date is parsed, When the value is invalid or ambiguous, Then the record is rejected and logged; valid dates are stored in ISO 8601 with timezone normalization. Given contract_term is ingested, When the value is not one of the accepted terms (monthly, quarterly, annual, multi-year with months), Then the record is flagged for review and excluded from scoring. Given any sync run, When it completes or fails, Then metrics are emitted: start/end time, records fetched/processed/succeeded/failed, error rate, and next scheduled run. Given error rate exceeds 2% or no progress is made for 15 minutes during an active sync, When monitoring evaluates, Then an alert is sent to configured channels (email/webhook) and the run is marked Needs Attention with actionable error summaries. Given an operator downloads errors, When export is requested, Then a CSV of failed records with connector, environment, and error details is available.
Downstream Availability to Renewal Lens Scoring
Given a connected and mapped account, When ARR, renewal_date, contract_term, and (if provided) health_score are updated via sync or webhook, Then the Renewal Lens score for related themes is recalculated within 10 minutes at the 95th percentile. Given account identity changes (merge/unmerge), When the canonical account changes, Then associated feedback and Renewal Lens scoring move to the canonical account without duplication or loss. Given a connector is paused or sandbox inclusion is disabled, When new data arrives, Then Renewal Lens scoring excludes those updates and the UI shows a Data Freshness badge indicating last included update time.
Feedback-to-Account Attribution
"As a product manager, I want each piece of feedback tied to the right account so that Renewal Lens can accurately attribute revenue risk to themes."
Description

Create a reliable mechanism to associate feedback items (reviews, tickets, emails, chats) with the correct customer account using signals such as email domain, user-to-account relationships from CRM, support system account IDs, and product usage metadata. Provide confidence scoring, collision handling for shared domains, and fallback heuristics. Enable manual override and bulk reconciliation workflows. Persist per-item attribution for traceability and allow reprocessing when mappings change. Expose attribution data to the scoring engine and UI.

Acceptance Criteria
Primary Account Match via Multi-Source Signals
Given a feedback item contains one or more of: support system account ID, CRM user ID, email address, product usage user ID When the attribution service processes the item Then the item is attributed to exactly one account when at least one high-authority signal is present according to precedence: support account ID > CRM user-to-account mapping > product usage user-to-account mapping > email domain match And the selected account equals the account resolved by the highest-precedence available signal And the attribution status is set to Attributed And p95 processing latency per item is ≤ 200 ms at 100 items/sec
Confidence Scoring and Thresholding
Given candidate matches are identified for a feedback item When the confidence score is computed on a 0.00–1.00 scale Then the score reflects weighted evidence from signals and is persisted with two-decimal precision And if score ≥ 0.80 the item is auto-attributed; if 0.50–0.79 it is marked Needs Review; if < 0.50 it is Unattributed And the thresholds are configurable and default to 0.80 and 0.50 and are retrievable via configuration API
Shared and Generic Domain Collision Handling
Given the email domain matches multiple accounts or is a generic provider domain When processing attribution where only the domain signal is present Then the system does not auto-attribute based solely on the domain And the item is routed to Needs Review unless corroborated by at least one non-domain signal for the same account And the generic domain list is configurable and defaults include gmail.com, yahoo.com, outlook.com, icloud.com
Fallback Heuristics Without Direct Identifiers
Given no high-authority signals (support account ID, CRM mapping, product usage mapping) are present When fallback heuristics are applied Then the system attempts alias resolution (email alias to primary), billing email-to-account match, recent IP-to-account match, and OAuth domain claim And if two or more heuristics indicate the same account, the item is attributed with confidence ≥ 0.70 and reason = "heuristic-agreement" And if fewer than two heuristics corroborate, the item remains Unattributed with reason = "insufficient-evidence"
Manual Override with Audit Trail
Given a user with role Product Manager or Admin opens a feedback item's attribution When they manually select a different account and provide a reason Then the new attribution is saved immediately and marked assigned_by = "manual" and confidence_score = null And the previous attribution is preserved in an immutable history with timestamp, actor, prior account_id, new account_id, reason And the change triggers re-scoring of affected themes and Renewal Lens within 60 seconds
Bulk Reconciliation at Scale
Given a user selects up to 10,000 feedback items by filter and defines a bulk mapping rule When the bulk reconciliation job is submitted Then items are re-attributed according to the rule with progress reporting (processed, succeeded, failed counts) And p95 throughput is ≥ 2,000 items per minute and job completes without timeouts And failures include item IDs and error reasons downloadable as CSV, and an undo action is available for 24 hours
Reprocessing on Mapping Changes and Data Exposure
Given a CRM user-to-account mapping changes or a domain alias is added/removed When the change event is received from the integration sync Then affected feedback items are reprocessed and updated within 15 minutes, with prior and new attributions versioned in history And the attribution fields (account_id, account_name, attribution_source, confidence_score, assigned_by, updated_at) are exposed via API and displayed in the UI on item detail And the scoring engine and Renewal Lens consume the updated attribution on the next cycle (≤ 15 minutes) and exclude items below auto-assign threshold from revenue-impact boosting
Revenue-Weighted Scoring Engine
"As a PM, I want a composite theme score that factors in ARR, health, and renewal date so that revenue-critical fixes rise to the top."
Description

Extend the existing theme ranking pipeline with a configurable scoring model that blends base theme strength with ARR weighting, renewal proximity boosting, and health score modifiers. Support tunable weights, non-linear functions (e.g., sigmoid or exponential decay by days-to-renewal), floors/caps to prevent outliers, and segment-specific configurations. Output a composite score with component breakdowns and confidence. Provide backfill of historical themes and incremental updates on data changes. Ensure performance at current scale and include unit/integration tests for reproducibility.

Acceptance Criteria
Configurable Composite Score Calculation
Given active configuration provides weights for base, ARR, renewal, and health components and a global score clamp range [0,1] When a theme is scored with base_strength, arr_normalized, renewal_boost, and health_modifier inputs Then the composite_score is computed using the configured weights and functions and is clamped to [0,1] And the component_breakdown includes pre_weight, weight_applied_value, post_weight_value for base, arr, renewal, and health And the response includes config_version and timestamp used for the calculation And updating weights in the configuration store results in new scores reflecting the change within 60 seconds
Renewal Proximity Boost Non-Linear Functions
Given the renewal boost function type is set to sigmoid with parameters (k, midpoint) and a fixture of days_to_renewal inputs When the renewal_boost is computed Then outputs match the reference implementation within an absolute tolerance of 1e-6 and are strictly monotonic decreasing with increasing days_to_renewal Given the function type is set to exp_decay with parameter lambda and the same fixtures When the renewal_boost is computed Then outputs match the reference vectors within 1e-6 and are strictly monotonic decreasing Given an invalid function type is configured When the engine initializes Then the engine falls back to the configured default function and emits a structured warning without failing scoring
Outlier Protection via Floors and Caps
Given component caps/floors are configured (e.g., cap_arr_component, cap_total_score, floor_total_score) When scoring themes with extreme ARR values or base_strength values Then the arr component contribution is limited to cap_arr_component and the composite_score is within [floor_total_score, cap_total_score] And increasing ARR beyond the cap does not change the composite_score by more than 1% relative And rank ordering among themes unaffected by caps remains unchanged
Segment-Specific Configuration Resolution
Given segment-specific configurations exist for SMB and Enterprise and the resolution_strategy is set to weight_by_arr When a theme is tied to multiple accounts across segments Then the effective weights and functions are computed as an ARR-weighted blend of the segment configurations And for a single-segment theme the exact segment configuration is applied And changing the resolution_strategy to dominant_segment applies the dominant segment’s configuration to subsequent scores within 60 seconds
Score Breakdown and Confidence API Contract
Given the scoring API endpoint GET /v1/themes/{id}/score is called with a valid theme id When the score is returned Then the payload contains composite_score, confidence, components.{base,arr,renewal,health}, weights, functions, config_version, and scored_at And confidence is in [0,1] and equals the upstream clustering confidence for the theme id And the response schema validates against score.v1 JSON schema and excludes nulls for required fields And P95 latency is <= 200 ms for cached themes and <= 500 ms for uncached themes under nominal load
Historical Backfill and Idempotency
Given a backfill is triggered for a specified date range and configuration version When the job completes Then 100% of themes in the range are re-scored and persisted with the requested config_version And re-running the same backfill produces byte-identical composite_score outputs and identical rank ordering (idempotent) And the backfill supports checkpointing and resumes from the last successful checkpoint after a failure And throughput is >= 2,500 themes per minute with job-level error rate <= 0.1% and any failed items retried up to 3 times
Incremental Updates, Ordering Stability, and Determinism
Given ARR, health score, renewal date, or segment data for an account changes When the change event is ingested Then all impacted theme scores are recomputed and reflected in the ranked queue within 5 minutes end-to-end at P95 And themes with unchanged inputs maintain their relative order before and after re-scoring And given identical inputs and configuration, repeated scoring runs produce byte-identical outputs and stable ordering in unit and integration tests And the scoring module has >= 90% line coverage and includes snapshot tests for the top-100 ranked queue by segment
Renewal Lens UI & Filters
"As a PM, I want a clear UI to see which themes are boosted by renewals and why so that I can quickly focus on what protects revenue."
Description

Introduce a toggleable Renewal Lens view in the theme list and live theme map that highlights boosted themes. Add filters for renewal window (e.g., 30/60/90 days), ARR bands, health status, and segments. Display badges and tooltips that show the score contribution from ARR, renewal proximity, and health. Provide sort-by composite score and quick actions (e.g., one-click sync) from the list. Ensure responsive design and accessibility, and preserve user preferences across sessions.

Acceptance Criteria
Toggle Renewal Lens in Theme List and Live Theme Map
- Given I am on the theme list or live theme map and the Renewal Lens feature flag is enabled, When the Renewal Lens toggle is visible, Then its default state is Off unless a saved preference exists. - Given Renewal Lens is toggled On, When the view loads or the user navigates between list and map, Then boosted themes are visually highlighted with a "Renewal Boost" badge and the "Renewal Lens" view label is displayed. - Given Renewal Lens is toggled On, When the user toggles it Off, Then highlights and Renewal Lens labels disappear and base rankings are restored within 500 ms. - Given the toggle state changes, When the user remains in the same session or navigates within the app, Then the toggle state remains consistent across list and map views. - Given a user without access, When the feature flag is disabled, Then the Renewal Lens toggle is not rendered.
Multi-Filter: Renewal Window, ARR Bands, Health, Segments
- Given Renewal Lens view is On, When the user opens Filters, Then options include Renewal Window (30/60/90 days and Custom), ARR Bands (<$10k, $10k–$50k, $50k–$250k, >$250k), Health (Healthy, At Risk, Critical), and Segments (multi-select). - Given one or more filters are selected, When the results refresh, Then only themes with contributing feedback from accounts matching ALL selected criteria are shown and a Filter badge shows the count of active filters. - Given the user chooses a Custom renewal window, When a start and end date are set, Then themes tied to accounts with renewal dates within that inclusive range are included. - Given filters are active, When the user clicks "Clear all," Then all filters reset, results revert, and the filter badge count returns to 0. - Given filters yield no results, When results refresh, Then a "No themes match your filters" empty state is shown with a "Clear filters" action.
Badges and Tooltips for Score Contribution
- Given Renewal Lens is On, When a theme is displayed in the list or map, Then a composite score badge is shown alongside an info icon or hover target. - Given the user hovers or focuses the score badge, When the tooltip opens, Then it lists contributions for ARR, Renewal Proximity, and Health with individual values and percentages that sum to the composite score. - Given the tooltip is open, When the user presses Esc, moves focus away, or taps outside on mobile, Then the tooltip closes. - Given the tooltip would render off-screen, When it opens, Then it repositions to remain within the viewport. - Given accessibility needs, When navigating via keyboard, Then the tooltip is reachable via focus and announced with role="tooltip" and descriptive text.
Sort by Composite Score with Deterministic Tie-Breakers
- Given themes are displayed, When the user selects "Sort by Composite Score" from the sort menu, Then themes reorder by descending composite score by default. - Given "Sort by Composite Score" is active, When the user toggles sort direction, Then the order switches between Desc and Asc. - Given themes have equal composite scores, When sorting is applied, Then ties break by higher ARR, then sooner renewal date, then alphabetical theme title. - Given a sort option is selected, When the user navigates between list and map, Then the sort selection persists.
One-Click Sync to Jira/Linear from Theme List
- Given a theme row is visible, When the user clicks "Sync" and at least one destination (Jira or Linear) is connected, Then an issue is created with a prefilled title "[Theme] — Composite Score: {score}" and a link back to EchoLens within 2 seconds. - Given both Jira and Linear are connected, When the user clicks "Sync," Then the last-used destination is applied; a destination switcher is available to change it. - Given a sync completes, When the UI updates, Then a success toast appears and the theme row displays an external issue badge with the destination icon and issue key; clicking it opens the issue in a new tab. - Given a sync fails, When the error is returned, Then an error toast with the reason and a "Retry" action is shown; no duplicate external issue is created. - Given an issue was created for a theme and destination within the last 24 hours, When the user attempts another one-click sync, Then a confirmation prompt warns about duplicates with options "Proceed" and "Cancel."
Responsive Design and Accessibility Compliance
- Given the app is viewed on desktop (>=1200px), tablet (768–1199px), and mobile (<=767px), When the Renewal Lens list and map are loaded, Then all controls (toggle, filters, sort, sync) are visible and usable with no horizontal scroll and readable text (>=16px on mobile). - Given touch devices, When interacting with controls, Then touch targets are at least 44x44 px and tooltips convert to tap-activated popovers. - Given keyboard-only navigation, When traversing the UI, Then focus order is logical, all interactive elements are reachable, and visible focus states are present. - Given color contrast requirements, When viewing highlights and badges, Then contrast ratios meet WCAG 2.1 AA and information is not conveyed by color alone. - Given assistive technologies, When elements are announced, Then ARIA roles, names, and states are provided for toggles, filters, tooltips, badges, and sort controls.
Preference Persistence Across Sessions
- Given a signed-in user adjusts view mode (Renewal Lens On/Off), filters, or sort, When they return in a new session or on another device within the same workspace, Then the last-used preferences auto-apply within 1 second of page load. - Given preferences are saved, When the user switches workspaces, Then only the preferences associated with the selected workspace apply. - Given preferences exist, When the user clicks "Reset to defaults," Then Renewal Lens turns Off, filters clear, sort resets to default, and the saved preferences are overwritten. - Given the Renewal Lens feature is disabled by admin, When a saved preference would turn it On, Then the view loads with Renewal Lens Off and no errors occur.
Proactive Renewal Risk Alerts
"As a CX lead, I want proactive alerts about renewal-risk themes so that my team can act before contracts are jeopardized."
Description

Enable configurable Slack and email alerts when themes exceed a revenue-risk threshold or when high-ARR accounts near renewal are contributing to unresolved themes. Support per-segment channels, batching and deduplication, daily/weekly digests, quiet hours, and unsubscribe controls. Include deep links to the Renewal Lens view and contextual metadata (ARR at risk, days to renewal, top accounts). Provide alert rules management and audit of deliveries and failures.

Acceptance Criteria
Threshold-Based Revenue Risk Alert Trigger
Given an admin sets a revenue-risk threshold and selects Slack and email destinations for a rule When a theme’s revenue-risk score equals or exceeds the threshold Then an alert is dispatched to the configured Slack channel(s) and email recipients within 5 minutes Given a rule with a minimum persistence window of 10 minutes When the theme’s score falls back below the threshold before the window elapses Then no alert is sent Given the rule is disabled When a theme crosses the threshold Then no alert is sent Given an alert was sent for a theme and rule When the theme remains above threshold without further change Then no additional alerts are sent for that theme and rule within 24 hours
Near-Renewal High-ARR Unresolved Theme Alerts
Given “high ARR” is defined as an ARR threshold and “near renewal” as a days-to-renewal threshold When an unresolved theme includes contributions from one or more accounts meeting both thresholds Then an alert is sent within 5 minutes including aggregate ARR at risk and the minimum days to renewal Given multiple qualifying accounts contribute to a theme When sending the alert Then include the top 5 accounts by ARR and total ARR at risk Given a theme becomes resolved When evaluating subsequent near-renewal conditions for that theme Then suppress near-renewal alerts for that theme
Segment-Based Routing to Slack Channels and Email Lists
Given segment-to-destination mappings are configured When an alert is generated for a theme tagged with a segment Then it is delivered to that segment’s Slack channel(s) and email list(s) Given a theme has multiple segment tags When an alert is generated Then send one message per destination mapping without duplicating messages within the same destination Given a mapping is removed or disabled When a qualifying alert is generated for that segment Then it is not delivered to the removed or disabled destinations
Alert Batching and Deduplication
Given a batching window of 10 minutes is configured When multiple alerts to the same destination are generated within the window Then they are combined into a single message listing each theme with ARR at risk and top accounts Given deduplication is enabled with a 24-hour window per theme and rule When the same theme re-qualifies within the window without a 25% or greater increase in ARR at risk Then suppress the duplicate alert Given the ARR at risk for a previously alerted theme increases by at least 25% When the theme re-qualifies within the dedup window Then send an updated alert highlighting the ARR delta
Alert Payload, Metadata, and Deep Links
Given an alert is generated When it is delivered Then it includes the theme name, aggregate ARR at risk, up to 5 top contributing accounts, days to renewal per listed account, the current revenue-risk score, and a deep link to the Renewal Lens view filtered to the theme Given the deep link is clicked When the Renewal Lens opens Then it loads with the theme pre-selected and displays contributing accounts and scoring context Given an email alert is delivered When rendered in common clients Then the subject and body include the theme name and ARR at risk, and the link uses HTTPS with UTM parameters for source attribution
Digests, Quiet Hours, and Unsubscribe Controls
Given daily and weekly digest schedules are configured When the digest time occurs Then send a digest summarizing the top N revenue-risk themes per segment with aggregate ARR at risk and deep links Given quiet hours are configured for a destination When an alert is generated during quiet hours Then suppress real-time delivery and queue the item for inclusion in the next digest Given a user clicks an unsubscribe link from email alerts When future alerts or digests are generated Then that user no longer receives email alerts while Slack channel deliveries continue Given a user re-subscribes via preferences When subsequent alerts or digests are generated Then the user receives emails starting with the next event or digest
Alert Rules Management and Delivery Audit
Given a user with Admin role When they create, edit, enable or disable, or delete an alert rule Then changes are persisted, versioned, and immediately applied Given a test action is triggered on a rule When executed Then a test alert is delivered to the configured destinations and recorded as a test in the audit log Given any alert delivery attempt When it succeeds or fails Then an audit record is stored with timestamp, rule ID, destination, payload hash, outcome, error message on failure, and retry count Given a transient delivery failure occurs When the retry policy is applied Then the system retries up to 3 times with exponential backoff and records each attempt Given a user filters the audit log by rule, destination, status, or date range When results are displayed Then they include accurate counts and support CSV export
Scoring Explainability & Auditability
"As a product leader, I want to understand and justify why a theme is prioritized by Renewal Lens so that I can defend decisions and tune the model responsibly."
Description

Provide transparent explanations for each boosted theme, including per-component score breakdowns, contributing accounts, and data timestamps. Add an audit log for scoring configuration changes and a sandbox to simulate different weight settings with side-by-side comparisons before applying. Allow export of explanations and logs for leadership reviews. Include guardrails (caps, minimum evidence thresholds) and monitoring dashboards to detect anomalies or model drift.

Acceptance Criteria
Score Breakdown Panel for Boosted Themes
Given a boosted theme in the Renewal Lens queue When the user selects "Explain score" Then the panel displays Base score, ARR component, Health component, Renewal proximity component, Guardrail adjustments, and Final score as numeric values with two-decimal precision And the sum of Base + components + adjustments equals the Final score within 0.01 tolerance And each component row shows the raw feature value, the weight used, and the computed contribution And a list of contributing accounts is shown with account_id, ARR, health_score, renewal_date, contribution_percent, evidence_count, and data_source And "Last data ingestion" and "Last score computation" timestamps are shown in the user's timezone in ISO-8601 format And after a manual "Recompute" action the panel refreshes within 2 seconds without persisting any configuration changes
Immutable Audit Log for Scoring Configuration Changes
Given a user changes any scoring configuration (weights, guardrails, caps, thresholds) and clicks Save or Apply in sandbox When the change is committed Then an audit log entry is created with change_id, timestamp (UTC), actor (user id/email), environment, changed_fields (old_value -> new_value), and optional reason And the entry is immutable and queryable by date range, actor, and field name And exporting the log in CSV and JSON for a selected date range completes within 10 seconds and includes all fields And audit log records are retained for at least 365 days; deletions produce tombstone entries
Sandbox Simulation with Side-by-Side Ranking Comparison
Given the user opens the Scoring Sandbox and selects a scope of themes (default: top 500 by ARR impact in last 30 days) When the user adjusts ARR/Health/Renewal weights or guardrail values Then a side-by-side view renders Current vs Proposed rank, score, and per-component contributions for each theme And each row shows score_delta and rank_change with up/down indicators And clicking Apply requires confirmation, applies changes within 60 seconds, and writes an audit log entry And clicking Discard exits without persisting changes And recomputation for 500 themes completes in under 5 seconds And the comparison can be exported to CSV within 10 seconds
Exportable Theme Explanations for Leadership Review
Given one or more themes are selected and a date range is set When the user triggers Export Explanations and chooses CSV, JSON, or PDF Then the export includes for each theme: final score, per-component contributions, weights in effect, contributing accounts (account_id, ARR, health_score, renewal_date, evidence_count), and data timestamps And the export reflects the selected date range and user timezone And file generation completes within 10 seconds for up to 1,000 themes And the export file is downloadable and shareable via a secure URL that expires within 24 hours
Guardrails: Caps and Minimum Evidence Thresholds Enforcement
Given scoring guardrails are configured with a max boost cap and a minimum evidence threshold When scores are computed Then no theme’s boosted score exceeds base_score increased by the configured cap percentage And a theme is only boosted if it has evidence from at least the configured number of distinct accounts within the configured lookback window And themes failing guardrails are labeled "Insufficient evidence" and are not boosted; the reason is shown in the explanation panel And guardrail configuration changes are auditable and take effect within 60 seconds of Apply
Monitoring Dashboard for Anomalies and Model Drift
Given the Scoring Health dashboard is opened When the dashboard loads Then it shows daily metrics: count of boosted themes, average boost %, score distribution, ARR impacted, and a 7-day vs 28-day baseline comparison And anomalies are flagged when any metric’s z-score exceeds 3.0 or the week-over-week change exceeds 30%, with visual badges and tooltips And users can drill down to affected themes and contributing accounts from an anomaly card And alert rules can be configured to send email/Slack notifications when an anomaly persists for 2 consecutive days And dashboard data refreshes at least hourly and shows last refreshed timestamp
Issue Tracker Sync with Renewal Context
"As a PM, I want synced issues to carry renewal context so that engineering understands the revenue urgency without extra back-and-forth."
Description

Enhance one-click Jira/Linear sync to include Renewal Lens context: ARR at risk, renewal date window, health status, contributing accounts, and score breakdown. Apply standardized labels (e.g., "Renewal-Risk", window buckets), auto-assign suggested owners, and populate custom fields. Support bi-directional status updates and link back from the issue to the theme with live refresh. Respect field schemas per project and fail gracefully with validation errors.

Acceptance Criteria
One-click Jira sync includes Renewal Lens context fields
Given a theme in EchoLens has Renewal Lens data (ARR at risk > 0, renewal date, health status, contributing accounts, score breakdown) and Jira integration is connected with field mappings, When the user clicks "Sync to Jira" and selects a valid project and issue type, Then one Jira issue is created and all of the following are true: - ARR at risk custom field equals the sum of ARR for contributing accounts in USD, matching EchoLens to the dollar. - Renewal window custom field is set to one of [0-30d, 31-60d, 61-90d, 90d+] based on the nearest renewal date. - Health status custom field equals the theme health in EchoLens (At Risk, Warning, Healthy). - Score breakdown custom field lists component scores whose total equals the displayed theme score. - Contributing accounts custom field lists account names and ARR values; count matches EchoLens. - The issue description contains a backlink URL to the EchoLens theme; a web link is also added to the issue. - No required Jira field is left empty; issue creation returns 201/Success.
One-click Linear sync includes Renewal Lens context fields
Given a theme in EchoLens has Renewal Lens data and Linear integration is connected with field mappings, When the user clicks "Sync to Linear" and selects a valid team and issue type, Then one Linear issue is created and all of the following are true: - ARR at risk custom field equals the sum of ARR for contributing accounts in USD, matching EchoLens to the dollar. - Renewal window custom field is set to one of [0-30d, 31-60d, 61-90d, 90d+]. - Health status custom field equals the theme health in EchoLens (At Risk, Warning, Healthy). - Score breakdown custom field lists component scores whose total equals the theme score. - Contributing accounts field lists account names and ARR values; count matches EchoLens. - The issue includes a backlink to the EchoLens theme in the description and as an external link. - Issue creation returns success and no required Linear field is empty.
Apply standardized labels and renewal window bucket on sync
Given standardized labels are enabled and a theme has at least one contributing account flagged At Risk and a nearest renewal date within 0-30 days, When the theme is synced to Jira or Linear, Then the created issue includes labels: - EchoLens-Theme - Renewal-Risk - Renewal-Window:0-30d (or the correct bucket among 31-60d, 61-90d, 90d+ based on nearest renewal date) And when the same theme is re-synced to the same external issue, Then labels are idempotent (no duplicates) and updated to the correct bucket if the renewal window has changed.
Auto-assign suggested owner on external issue creation
Given the theme has a suggested owner resolved to a valid Jira/Linear user with assign permission in the selected project/team, When the issue is created via one-click sync, Then the issue assignee equals the suggested owner. Given the suggested owner cannot be resolved or lacks assign permission, When the issue is created, Then the issue remains Unassigned and a comment is added noting the assignment fallback and the suggested owner value from EchoLens.
Bi-directional status updates with live theme refresh
Given a theme is linked to a Jira/Linear issue via sync, When the external issue status transitions (e.g., To Do -> In Progress -> Done), Then the EchoLens theme reflects the corresponding status within 2 minutes and records the external status and timestamp. Given the EchoLens theme status is changed by a user (e.g., Triaged -> In Delivery -> Shipped), When sync is enabled for that link, Then the external issue status is updated to the mapped status within 2 minutes (subject to permissions) and the change is logged. Given the external issue contains the backlink to the EchoLens theme, When a user opens the link, Then the theme page loads successfully (HTTP 200) and displays current Renewal Lens data (last updated timestamp within the past 5 minutes).
Project field schema validation and graceful failure
Given the selected Jira/Linear project has required fields or custom field types that do not match the configured mappings, When the user attempts one-click sync, Then no external issue is created (all-or-nothing), and EchoLens surfaces a validation error that includes: project/team key, field name(s), expected type(s), and guidance to fix mappings. And no partial updates occur; the theme remains unchanged and no labels/links are added externally. And the user can retry after correcting mappings, resulting in successful issue creation.
Resync updates external issue with changed Renewal Lens context
Given an external issue is already linked to a theme via sync, When the theme’s Renewal Lens data changes (e.g., ARR at risk total, nearest renewal date window, health status, score breakdown) and the user clicks Resync, Then the external issue is updated in place with the new values for all mapped fields, and labels/buckets are adjusted accordingly without duplication. And the issue’s change log shows updated field values; the backlink remains unchanged. And the update completes successfully without creating a new issue.

Segment Simulator

Try “what-if” mixes of revenue, plan, and stage weights with sliders to preview queue reordering, ARR protected, and segment coverage—then commit the chosen mix and push issues to Jira/Linear with one click. Aligns stakeholders quickly without spreadsheet debates.

Requirements

Interactive Weight Sliders
"As a PM or CX lead, I want to adjust segment weights with simple sliders so that I can quickly explore different prioritization mixes without building spreadsheets."
Description

Provide accessible, real-time sliders to adjust weightings for revenue (ARR), plan tier, and customer stage within defined bounds and presets. The control set must support keyboard and mouse input, numeric entry, reset-to-default, and named presets (e.g., Revenue-heavy, Balanced, Adoption-first). Changes should debounce and emit a normalized parameter object to the simulation engine. Validation prevents invalid totals or out-of-range values, with inline feedback. State persists per user and per workspace, and integrates with role-based permissions to determine who can edit vs. view.

Acceptance Criteria
Mouse and Keyboard Interaction on Sliders
Given the Segment Simulator is open with Revenue, Plan Tier, and Customer Stage sliders visible and enabled When the user drags a slider thumb with the mouse or clicks on the track Then the slider value updates in real time (visual feedback within 100ms) and snaps to the configured step Given focus is on a slider When the user presses Arrow keys Then the value changes by one step per keypress; PageUp/PageDown change by 10 steps; Home/End set to min/max Given a slider has focus When it is operated via keyboard Then it exposes role="slider" with correct aria-valuemin/aria-valuemax/aria-valuenow and a visible focus indicator meeting WCAG 2.2 AA
Numeric Entry and Formatting for Weights
Given each weight has an associated numeric input field When a user types a numeric value and commits via Enter or blur Then the slider syncs to that value, clamped to bounds and rounded to the nearest step Given the user types non-numeric or partial input When commit is attempted Then the field prevents commit and displays inline validation until the entry is corrected Given a valid number is entered When it is committed Then the displayed value is formatted consistently with at most two decimal places
Applying Named Presets and Reset to Default
Given named presets Revenue-heavy, Balanced, and Adoption-first are available When the user selects a preset Then all three weights update atomically, the selected preset is marked active, and the configuration name is shown as the Active Preset Given a preset is active When the user adjusts any weight Then the preset indicator switches to Custom Given a default preset is defined When the user clicks Reset to Default Then the default preset is applied and all dependent outputs update once due to debouncing
Debounced Emission of Normalized Parameters
Given the user is adjusting weights When changes occur rapidly Then the system emits a single normalized parameter object to the simulation engine no more often than every 300ms after the last change Given a change is committed (slider release, numeric commit, or preset apply) When 300ms elapse without further input Then the emitted object has shape { revenue, planTier, stage } with each value in [0,1] and a total that sums to 1.0 ± 0.001 Given an emission occurs When the simulation engine receives it Then the queue preview reflects the change within 500ms
Validation of Bounds and Total with Inline Feedback
Given configured bounds for each weight and a rule that the total must equal 100% When the user attempts to set a value outside bounds or causes the total to not equal 100% Then the offending control shows inline error messaging and an aria-live announcement, and any Commit/Apply action is disabled Given the user corrects the values so they are within bounds and the total equals 100% When validation passes Then all error messaging clears immediately and disabled actions re-enable Given invalid numeric input is present When the state is invalid Then no parameter object is emitted to the simulation engine
Per-User and Per-Workspace State Persistence
Given a signed-in user in workspace A When the user changes weights or selects a preset Then the state is saved per user per workspace and restored on reload and subsequent sessions before the first user interaction Given the same user switches to workspace B When the controls are displayed Then the last saved state for workspace B is shown; switching back to A restores A’s state Given the user opens EchoLens on a different device When they load the Segment Simulator Then the last saved state for their account and current workspace is applied
Role-Based Permissions for Edit vs View
Given role-based permissions where Editors can edit and Viewers cannot When a Viewer opens the Segment Simulator Then all sliders and numeric inputs are read-only or disabled with a tooltip explaining View-only permissions, and no emissions occur from attempted interactions Given an Editor opens the Segment Simulator When they interact with sliders, numeric inputs, or presets Then interactions are enabled and behave as specified, and debounced emissions occur as expected Given a user’s role changes during a session When the permission update is received Then the UI transitions to the correct state within 1 second without requiring a reload
Live Queue Reordering Preview
"As a product manager, I want to see the queue reorder instantly as I change weights so that I can evaluate the impact before committing."
Description

Compute and render a dynamic preview of the prioritized queue as weights change, updating within 500 ms for the first 100 items and gracefully paginating beyond. Visually indicate rank deltas, confidence scores, and ties with clear annotations and tooltips. Provide stable sorting, sticky headers, and client-side caching to minimize flicker. Handle long-running computations with a loading state and stale data indicators. Expose an API endpoint for preview results to support both web UI and future integrations.

Acceptance Criteria
500ms Top-100 Preview Update
Given the user adjusts any Segment Simulator weight slider When the user stops adjusting for 150 ms or releases the control Then the first 100 items render with updated ranks within 500 ms at p95 across 30 trials in a clean browser session And the 100th row is visible without blank gaps or full-list flash And the Last updated timestamp reflects the new computation time within 100 ms accuracy And no console errors or unhandled promise rejections occur during the update
Graceful Pagination Beyond 100 Items
Given the queue contains more than 100 items When the preview renders Then pagination controls are present with a default page size of 100 and options for 100/200/500 And navigating next/previous page loads within 300 ms at p95 without resetting document scroll position And ranks are continuous across pages with no duplicates or gaps (e.g., page 1 ends at 100, page 2 begins at 101) And previously visited pages render from client cache without a network request and without visible flicker And weight updates refresh the currently visible page while keeping page index and in-list scroll position
Rank Delta, Confidence, and Tie Annotations
Given a preview update that changes item ordering When the list re-renders Then each row shows a rank delta indicator (up/down/no change) and numeric delta value And each row shows a confidence score as a percentage with one decimal place and a tooltip revealing exact value And items with equal scores display a Tie badge and share the same rank with deterministic sub-ordering And all indicators are keyboard focusable and have accessible labels meeting WCAG 2.1 AA And hovering or focusing an indicator shows its tooltip within 150 ms
Stable Sorting Across Sequential Updates
Given two sequential preview computations with identical inputs (weights and data unchanged) When results are compared Then the item order is identical, including within tie groups, across three consecutive runs And tie-breaking is deterministic using immutable fields (e.g., item id ascending) so relative order does not oscillate And changing weights back to prior values yields the same order as the original run
Sticky Headers and Minimal Flicker via Client Caching
Given the queue preview is scrolled vertically When the user scrolls within the list Then the header row remains visible (sticky) without overlap or jitter at sm, md, and lg breakpoints And rapid slider adjustments (≤ 5 changes/second for 5 seconds) do not cause full-list flash; unchanged rows retain DOM identity (stable element keys) And average frame rate remains ≥ 50 FPS during updates on the reference test device
Loading State and Stale Data Indicators
Given a weight change triggers a recomputation expected to exceed 300 ms When the computation is in progress Then a non-blocking loading indicator appears within 300 ms and remains until fresh data arrives And the current list is marked Stale with a timestamp while new results are computing And if multiple weight changes occur, only the latest request's results are applied; earlier in-flight responses are discarded (latest-wins) And upon completion the stale indicator clears and the Last updated time reflects the completed computation
Preview API Endpoint for Integrations
Given a GET request to /api/preview with valid weights, limit (≤ 500), offset, and authentication When the request is processed Then the response is 200 within 400 ms at p95 for limit=100 and includes JSON fields: items[id, title, score, confidence, rank, previousRank, rankDelta, tieGroup], pagination[limit, offset, total], metadata[lastUpdated] And repeated requests with identical parameters return identical ordering and matching ETag/Last-Modified headers And invalid parameters return 400 JSON error; unauthenticated returns 401; rate-limited returns 429 with Retry-After And CORS is enabled for allowed origins and the endpoint is documented in a versioned OpenAPI spec (/v1/preview)
ARR Protection and Segment Coverage Panel
"As a stakeholder, I want visibility into protected ARR and segment coverage under a given mix so that I can justify prioritization decisions with clear business impact."
Description

Display business impact metrics derived from the simulated mix, including protected ARR, percent of revenue covered in the top N, distribution by plan and stage, and trend versus current baseline. Provide threshold guardrails and warnings (e.g., coverage below X% for Enterprise). Include downloadable snapshots and hover drill-down to the customer cohort contributing to each metric. Calculations must be deterministic and sourced from the same metadata used by the scoring engine to ensure consistency.

Acceptance Criteria
Display Protected ARR and Top‑N Revenue Coverage
Given I have applied a simulated mix of revenue, plan, and stage weights And I have selected a Top N value When the simulator recalculates the prioritized queue Then the panel displays Protected ARR equal to the sum of unique account ARR linked to issues within the Top N And Percent of Revenue Covered in Top N equals (Protected ARR / Total In‑Scope ARR) × 100, rounded to one decimal place And values update within 1 second after adjusting any slider or Top N control And currency values are formatted per workspace currency with thousands separators
Plan and Stage Distribution Accuracy
Given a simulated mix and Top N are active When the panel computes distribution by plan and by stage for the Top N cohort Then the sum of ARR share across all plans equals 100% ± 0.1% And the sum of ARR share across all stages equals 100% ± 0.1% And counts by plan and stage match the number of unique accounts in the Top N cohort And changing weights or Top N updates the distributions within 1 second
Trend vs Baseline Calculation and Visual Indicators
Given a current baseline (non‑simulated) exists for the same metadata_version_id And a simulated mix is applied When the panel computes trend deltas Then each metric shows absolute delta and percent change versus baseline, with up/down indicators And metrics with a zero baseline show N/A for percent change And hovering the delta shows a tooltip with baseline value, simulated value, and computation formula
Threshold Guardrails and Warnings for Segment Coverage
Given segment coverage thresholds are configured (e.g., Enterprise >= X%) And a simulated mix is applied When the computed coverage for any segment falls below its threshold Then the panel shows a warning badge in-line with the segment metric and in a summary alert And the warning text includes Segment Name, Current %, and Threshold % And when coverage is at or above threshold, no warning is shown And updating thresholds or the mix refreshes warnings within 1 second
Downloadable Snapshot with Provenance
Given a simulated mix and Top N are active When I click Download Snapshot Then a CSV file is downloaded named segment-simulator-snapshot-YYYYMMDD-HHMM.csv And the CSV contains: timestamp (ISO 8601 UTC), workspace ID, metadata_version_id, Top N, all current slider weights, Protected ARR, Percent Revenue Covered in Top N, plan distribution (count and ARR share), stage distribution (count and ARR share), and trend vs baseline values And when the CSV is re-aggregated, totals match on-screen metrics within rounding tolerance (± $0.01 and ± 0.1%) And metadata_version_id matches the scoring engine metadata version used for the simulation
Hover Drill‑Down to Customer Cohort
Given the metrics panel is visible When I hover over Protected ARR or Percent Revenue Covered Then a popover appears within 300 ms showing the contributing customer cohort for the Top N: Account Name, ARR, Plan, Stage And the popover includes the total account count and total ARR that equal the displayed metric within rounding tolerance (± $0.01) And the popover supports scrolling to at least 50 accounts without layout shift And moving the cursor away dismisses the popover within 300 ms
Deterministic, Consistent Calculations from Scoring Metadata
Given the same slider weights, filters, and Top N And the metadata_version_id has not changed When I recompute the simulation or reload the page Then all displayed metrics are identical to the previous run to two decimal places And the metadata_version_id displayed on the panel equals the scoring engine’s metadata version used to produce the prioritized queue And if the metadata_version_id changes between runs, the panel indicates recalculation occurred and values may differ
One-click Commit and Issue Sync
"As a PM, I want to commit the chosen mix and push the resulting issues to Jira or Linear with one click so that the team can act immediately without manual coordination."
Description

Enable committing a selected weight mix to become the active scoring profile, persisting versioned configuration with timestamp and author. Provide a single action to push the resulting prioritized items to Jira/Linear, including project/label mapping, assignee rules, and back-links to EchoLens. Include conflict detection (existing tickets, duplicates), idempotent retries, and clear error handling. Show a confirmation summary of changes (weights, top items affected) before execution and a success log afterward.

Acceptance Criteria
Commit Weight Mix as Active Scoring Profile
- Given a user with Manage Scoring Profiles permission and a selected weight mix in Segment Simulator, When the user clicks Commit as Active, Then a new scoring profile version is persisted with version ID, UTC timestamp, author user ID, and the exact weights committed. - Then the new version becomes the active scoring profile immediately and the previous active version is retained and marked inactive. - Then the prioritized queue is recalculated using the new active profile and reflects the change within 5 seconds. - Then the active profile metadata (version ID, author, timestamp) is visible in UI and retrievable via API. - Then users without required permission see the Commit action disabled and any direct API attempt returns 403.
Pre-Commit Confirmation Summary
- Given the selected weights differ from the current active profile, When the user clicks Commit, Then a modal displays: changed weights with deltas, a snapshot hash, and the list of top 20 items whose ranks will change including from->to positions and count entering top 10/25/50. - Then the modal provides Cancel and Confirm; Confirm uses the displayed snapshot hash to execute to prevent drift. - Then if there are no changes versus the active profile, the Confirm button is disabled and the modal states No changes to commit. - When the user clicks Confirm, Then the commit proceeds; When the user clicks Cancel, Then no changes are made.
One-Click Push to Jira and Linear with Mapping
- Given an active scoring profile and a configured destination (Jira and/or Linear) with project/team mapping, labels, and assignee rules, When the user clicks Push Issues, Then the top N prioritized items (user-selected or default N) are created or updated in the chosen destination(s). - Then each destination issue includes: EchoLens Item ID in a custom field or footer, a back-link to EchoLens, mapped project/team, mapped labels/tags (including EchoLens), and assignee determined by the configured rule. - Then if no destination is configured, the Push Issues button is disabled and the UI provides a link to configure settings. - Then for up to 100 items, the push completes within 60 seconds using batched requests that respect destination rate limits.
Conflict Detection and Deduplication
- Given one or more prioritized items already exist in the destination, When pushing, Then the system detects conflicts using EchoLens Item ID back-link/custom field or high-confidence title+project similarity (threshold ≥ 0.9). - Then detected conflicts default to Update existing rather than Create new and are listed in the preflight summary with their chosen action. - Then no duplicate issues are created for the same EchoLens item in the same destination. - Then if two EchoLens items match the same destination issue, the second is marked Skipped - potential duplicate with a link for manual merge, and is reported in the post-run log.
Idempotent Retries and Exactly-Once Creation
- Given a network error, timeout, or user-initiated retry, When the push is retried, Then per-item idempotency keys ensure items already created/updated are not duplicated and only missing items are processed. - Then multiple retries or repeated clicks of Push Issues result in the same final state with at most one destination issue per EchoLens item. - Then idempotency keys are stored for at least 24 hours per destination and include destination type, project/team, and EchoLens Item ID. - Then the run log clearly marks retried items as Skipped (already applied) or Updated (idempotent update).
Success Log and Audit Trail
- When a commit and/or push completes, Then a success log view shows: actions performed (commit, push), version ID and snapshot hash used, counts of created/updated/skipped/failed items, duration, and links to destination issues. - Then the log is persisted for at least 90 days and is retrievable via UI and API and exportable as CSV and JSON. - Then an audit trail records user, timestamp (UTC), affected projects/teams, and configuration version for both commit and push actions.
Error Handling and Partial Failure Recovery
- Given destination authentication is invalid or expired, When starting a push, Then the operation aborts before item processing and shows an actionable message with a Reconnect link; no destination issues are created. - Given per-item validation errors (e.g., missing required field mapping), When pushing, Then failing items are skipped with explicit error reasons while other items continue processing. - Given rate limit responses (HTTP 429), Then the system applies exponential backoff and retries up to 3 times per batch; remaining failures are reported with the final status. - Given the user cancels mid-run, Then the current batch completes, subsequent batches are not started, and the user can resume from the last successful batch with idempotency.
Scenario Save, Share, and Compare
"As a team lead, I want to save and share candidate mixes and compare them to the current baseline so that stakeholders can review options and reach alignment quickly."
Description

Allow users to save named scenarios capturing weight configurations, top-N snapshots, and key metrics. Support link-based sharing within the workspace, role-based access (view/comment/edit), and side-by-side comparison of scenarios versus the current baseline with diff of ranks and metrics. Provide preset templates and the ability to clone scenarios. Persist scenario history and enable quick restore to any prior configuration without affecting live weights until committed.

Acceptance Criteria
Save Named Scenario with Snapshot and Metrics
Given I have an active weight configuration and select a Top N value And I have permission to save scenarios When I click Save Scenario, enter a unique name, and confirm Then the system persists the scenario with the exact weight values, selected Top N, and a snapshot of the current top-N issues including IDs and ranks And the system persists key metrics for the snapshot including ARR protected and segment coverage And the scenario appears in the workspace scenario list with author and timestamp And a confirmation message is shown and unsaved-changes indicators are cleared
Unique Scenario Naming and Validation
Given a scenario name that already exists in the workspace (case-insensitive) When I attempt to save a new scenario or rename an existing one to that name Then the system blocks the action and displays a clear duplicate-name error And the system suggests an available name variant by appending a numeric suffix And no duplicate scenario is created or overwritten
Workspace Share Link with Role-Based Access
Given an existing saved scenario When the owner clicks Share and generates a link with a selected role (view, comment, or edit) Then the system produces a workspace-scoped link that requires authentication And users with view can open the scenario and compare but cannot modify weights or save And users with comment can add comments and annotations but cannot change weights or save And users with edit can modify weights and save new versions And non-workspace users or revoked links are denied access And the owner can revoke the link and access is immediately disabled
Side-by-Side Comparison with Rank and Metric Diff
Given a saved scenario and the current baseline When I open Compare against Baseline Then the UI displays both lists side-by-side for the same Top N And each issue shows rank deltas (up, down, new, removed) computed correctly between scenario and baseline And key metrics deltas (ARR protected, segment coverage) are displayed with absolute and percentage change And items missing from one list are clearly labeled as new or removed And the comparison uses the latest live baseline at the time of viewing
Preset Templates and Clone Scenario
Given I open the templates gallery When I select a preset template Then a new draft scenario is created with predefined weight values And the top-N snapshot and key metrics are computed for current data Given an existing saved scenario When I click Clone Then a new draft scenario is created with identical weight values and a suggested name "Copy of <original>" And the snapshot and metrics are recomputed at clone time And the original scenario remains unchanged
Version History and Quick Restore
Given a saved scenario with at least one prior version When I save changes to the scenario Then a new version entry is appended with author, timestamp, and summary of changes When I choose Restore on a prior version Then the simulator loads that version as the current draft for the scenario And a banner indicates the draft reflects a restored version And no changes are applied to the live baseline until commit
No Live Changes Until Commit and Commit Updates Baseline
Given any saved or draft scenario is open When I switch scenarios, restore a version, or close the simulator without committing Then the workspace baseline weights and live queue remain unchanged Given I have edit permission and a scenario is selected When I click Commit and confirm Then the workspace baseline updates to the scenario's weight configuration And the live queue reorders accordingly And the committed scenario is marked with a committed timestamp and actor
Approval Workflow and Audit Trail
"As an admin, I want an approval step with a detailed audit trail for scoring changes so that we maintain governance and traceability for compliance."
Description

Introduce an optional approval step for committing weight changes, configurable by workspace policy. Capture an immutable audit log of who proposed, reviewed, approved, and committed a mix, including before/after diffs, rationale notes, and linked pushes to Jira/Linear. Expose exportable logs for compliance and provide webhooks to notify Slack/Email on requests and approvals. Enforce permissions to ensure only authorized roles can approve and commit.

Acceptance Criteria
Workspace Approval Policy Configuration
- Given a workspace admin, When they open the Approval Policy settings, Then they can toggle "Require approval to commit weight mixes" on or off and save successfully. - Given the approval policy is enabled, When the settings page or policy API is reloaded, Then the persisted values are returned accurately. - Given the policy includes "Required approvers count" and "Eligible approver roles", When the admin updates these and saves, Then validation enforces approvers count >= 1 and roles are limited to permitted roles, and changes take effect immediately.
Permission Enforcement on Approve and Commit
- Given a user without the Approver role, When they attempt to approve a pending request, Then the action is blocked (403) and the UI control is disabled with a clear message. - Given a user without Commit permissions, When they attempt to commit an approved mix, Then the commit is denied and the attempt is logged in the audit trail. - Given a user with the appropriate role and permission, When they approve or commit, Then the action succeeds and is recorded in the audit trail.
Approval Request Lifecycle (Submit, Approve, Reject, Supersede)
- Given approval is required by policy, When a proposer submits a new weight mix, Then the system creates a Pending Approval request with a unique ID and commit is disabled until approval. - Given a pending request, When an approver approves with an optional note, Then the request transitions to Approved and the proposer/committer are notified. - Given a pending request, When an approver rejects with a required note, Then the request transitions to Rejected, commit remains disabled, and the proposer is notified. - Given an approved request, When a newer proposal for the same workspace is submitted before commit, Then the older request is auto-closed as Superseded and remains visible in the audit trail.
Immutable Audit Trail for Mix Changes
- Given any event {Proposed, Reviewed, Approved, Rejected, Committed}, When the event occurs, Then an append-only audit entry is created capturing event type, timestamp (UTC), actor ID, actor role, and request ID. - Given existing audit entries, When any user or API attempts to modify or delete an entry, Then the system prevents changes and records a Tamper Attempt entry with actor and timestamp. - Given an audit trail for a request, When fetched via UI or API, Then entries return stable IDs and are ordered chronologically with precise timestamps.
Before/After Weighted Mix Diff and Rationale Notes
- Given a proposal is submitted, When the audit entry is created, Then it includes before and after values for revenue, plan, and stage weights with field-level deltas. - Given a proposal or approval action, When a rationale note up to 2000 characters is provided, Then the note is stored and displayed within the audit timeline. - Given API access to an audit entry, When retrieving the diff, Then the response returns structured JSON of before/after values and computed delta per field.
Jira/Linear Push Association in Audit Entries
- Given a commit triggers one-click push to Jira/Linear, When the push succeeds, Then the commit audit entry stores the external system, batch ID, and links to each created/updated ticket. - Given the external push fails partially or fully, When the commit is attempted, Then the system reports failure, logs error details and a correlation ID in the audit trail, and does not mark the mix as Committed. - Given an audit log view is opened, When entries contain external links, Then the UI displays deep links to the external tickets and fetches current status on demand without blocking the log view.
Compliance Exports and Event Notifications
- Given a compliance user, When they export audit logs with filters (date range, request ID, actor, action), Then a downloadable CSV or JSON is generated containing matching entries including diffs and external links. - Given an export completes, When the file is opened, Then headers and data are UTF-8 encoded and match the on-screen audit data. - Given Slack webhook and email recipients are configured, When events {Request Submitted, Approved, Rejected, Committed} occur, Then Slack messages and emails are sent containing request ID, actor, action, and timestamp. - Given a notification delivery failure occurs, When it is detected, Then the system retries delivery at least 3 times with backoff and records final status in the audit trail.

ARR Sync

Continuously enriches feedback with ARR, plan, lifecycle stage, and renewal data from Salesforce/HubSpot and billing tools. De-dupes and reconciles conflicts so segment-aware scores stay accurate and up to date without manual maintenance.

Requirements

Unified CRM and Billing Connectors
"As a workspace admin, I want to securely connect our CRM and billing tools so that EchoLens can automatically enrich feedback with ARR, plan, stage, and renewal info."
Description

Implements secure, OAuth-based connectors to Salesforce, HubSpot, and major billing platforms such as Stripe, Chargebee, and Recurly to ingest account-level ARR, plan, lifecycle stage, renewal dates, and related identifiers. Supports real-time webhooks where available and rate-limit-aware polling with incremental cursors for systems without events. Normalizes core fields on ingestion, stores connection secrets securely, and provides multi-tenant isolation. Ensures reliable, continuous enrichment data flow into EchoLens so feedback items can be automatically annotated without manual exports.

Acceptance Criteria
CRM OAuth Connectors: Salesforce and HubSpot
Given an EchoLens org admin initiates Connect to Salesforce or HubSpot, When redirected to the provider OAuth consent screen, Then only read scopes required for Accounts/Companies, Deals/Opportunities, Users, and basic metadata are requested and displayed. Given consent is granted, When the OAuth callback is received, Then access and refresh tokens are stored encrypted at rest using a KMS-backed key; secrets are never logged; and connector status shows Connected with provider name, external org ID, and timestamp. Given the connector is active, When a token expires or is revoked, Then automatic refresh is attempted up to 3 times with exponential backoff; on failure the status changes to Action Required within 5 minutes and an alert is emitted to the org admins. Given the connector is active, When API calls are made, Then provider rate-limit headers (if available) are recorded in metrics and calls are throttled to remain below limits. Given an admin selects Disconnect, When confirmed, Then tokens are deleted from the secret store within 60 seconds, the provider app is deauthorized, the status becomes Disconnected, and no further API calls occur.
Billing Platform OAuth Connectors: Stripe, Chargebee, Recurly
Given an EchoLens org admin connects to Stripe, Chargebee, or Recurly, When OAuth is completed, Then only read scopes for customers, subscriptions, invoices, and products/plans are requested; tokens are stored encrypted at rest; and the connector shows Connected with account identifier and timestamp. Given a new billing connector is established, When initial sync starts, Then a backfill of all active subscriptions and all subscriptions updated in the last 24 months is queued and processed to completion without manual intervention, with progress visible to the admin. Given the connector is active, When the admin rotates credentials in the billing provider, Then EchoLens detects invalid tokens on next call, marks status Action Required within 5 minutes, and provides a Reconnect flow that restores sync without data duplication. Given the connector is active, When a soft-delete or archive occurs in the billing system, Then the change is reflected in EchoLens on the next sync and normalized records are marked inactive rather than hard-deleted.
Event Webhooks and Rate-Limit-Aware Polling with Incremental Cursors
Given a provider supports webhooks for account/company or subscription changes, When a relevant change occurs, Then EchoLens processes the webhook and updates normalized records within 2 minutes p50 and 5 minutes p95 end-to-end. Given a provider does not support webhooks, When polling is used, Then EchoLens polls at a configurable interval (default 10 minutes), uses an incremental cursor based on updated_at or equivalent, and achieves update latency of <=15 minutes p95 while remaining under provider rate limits. Given 429 or 5xx responses are returned, When retries are attempted, Then exponential backoff with jitter is applied, retries are capped at 3 attempts, and circuit-breaking prevents further calls for 60 seconds before reattempt. Given duplicate events or overlapping polling windows occur, When records are processed, Then idempotency is ensured using provider resource ID + updated_at versioning so that no duplicate normalized records are created. Given both webhook and polling are enabled, When events are missed, Then a daily reconciliation job detects and fills gaps by comparing last cursor positions without exceeding rate limits.
Field Normalization to Canonical Schema
Given records are ingested from CRM and billing providers, When normalization runs, Then fields are mapped to the canonical schema: account_id (EchoLens), crm_account_id, billing_customer_id, arr (number >= 0), plan (string), lifecycle_stage (enum: lead, prospect, customer, churned, other), renewal_date (ISO 8601 UTC date), currency (string ISO 4217 when provided), source (enum), last_synced_at (ISO 8601 UTC). Given provider-specific types and formats vary, When values are normalized, Then numbers are parsed with locale-agnostic rules, timestamps are converted to UTC ISO 8601, and nulls are preserved (no defaulting to 0 for missing numeric fields). Given multiple sources provide overlapping fields, When canonical values are emitted, Then the default precedence is: ARR and plan from billing if available else CRM; lifecycle_stage from CRM if available; renewal_date from billing if available else CRM; and provenance for each canonical field is recorded. Given invalid or unparsable values are encountered, When normalization fails for a field, Then the field is set to null, an error is logged with record identifier and source, and overall record ingestion continues without blocking the batch. Given normalization completes, When sampling 1,000 records per connector, Then 100% contain source and last_synced_at, and >99% of records with provider ARR present map to a valid numeric arr value.
Secure Secret Storage, Rotation, and Least-Privilege Scopes
Given any connector credential is created, When it is persisted, Then it is stored only in a managed secrets vault and encrypted with a KMS-managed key; plaintext secrets never appear in logs or analytics. Given access to secrets is requested by services, When IAM policies are evaluated, Then only the connector microservice role can read the specific tenant's secrets; access is denied to all other principals. Given an admin triggers credential rotation or provider forces token rotation, When rotation occurs, Then new credentials are adopted without downtime and old credentials are purged from the secret store within 60 seconds. Given security monitoring is active, When a secret access occurs, Then it is audit-logged with tenant, actor/service, purpose, and timestamp; anomalous access patterns trigger alerts to the security channel within 5 minutes. Given OAuth apps are configured, When scopes are requested, Then only documented least-privilege read scopes are used; any attempt to exceed the approved scope set fails CI checks and blocks deployment.
Multi-Tenant Isolation and Data Access Controls
Given multiple EchoLens tenants connect to the same external provider instance or to different instances, When data is ingested and stored, Then all records are partitioned by tenant_id and access-controlled so no cross-tenant reads or writes are possible. Given two tenants share identical external identifiers (e.g., the same domain or overlapping CRM IDs in sandbox vs prod), When enrichment runs, Then lookups are constrained to the tenant scope and do not leak or merge data across tenants. Given APIs and exports are used, When a tenant requests enriched data, Then only records with their tenant_id are returned; attempts to access another tenant's data return 403 and are logged. Given automated tests run in CI, When multi-tenant isolation tests execute, Then they seed overlapping IDs across tenants and assert zero cross-tenant results for 100% of queries and background jobs. Given observability is enabled, When a cross-tenant access is attempted or blocked, Then a security event is emitted with tenant IDs, actor, and action for investigation.
End-to-End Automatic Annotation of Feedback with Enriched Data
Given a feedback item is created in EchoLens and its author or account can be matched via CRM account ID, billing customer ID, or verified email domain mapping, When enrichment data is available, Then the feedback item is annotated with arr, plan, lifecycle_stage, renewal_date, and source identifiers within 5 minutes p95 of data arrival. Given account attributes change in CRM or billing, When a re-sync occurs, Then existing feedback items linked to that account are re-annotated to reflect new values within 10 minutes p95 without creating duplicate items. Given multiple connectors exist for a tenant, When annotation occurs, Then provenance is displayed per field (e.g., arr=billing:stripe, lifecycle_stage=crm:salesforce) and last_synced_at is visible. Given a feedback item cannot be matched to any account, When annotation is attempted, Then the item remains unannotated and shows a non-blocking reason of No account match; no incorrect defaults are applied. Given annotation jobs fail transiently, When retries run, Then they follow exponential backoff with a maximum of 3 attempts before surfacing an actionable error to the org admin.
Identity Resolution and De-duplication
"As a product ops manager, I want customer records from different systems to be matched and de-duplicated so that enrichment is accurate and consistent."
Description

Builds a matching engine that unifies accounts and contacts across CRM and billing systems using deterministic keys (external IDs, domain, CRM account ID) and fuzzy heuristics (name similarity, email domain). Creates a canonical customer entity with a stable internal ID, deduplicates conflicting records, and maintains a linkage graph for traceability. Supports manual merge/unmerge with safeguards to prevent double counting ARR and ensures enrichment attaches to the correct customer for every feedback item.

Acceptance Criteria
Deterministic Match Across Systems
Given records from Salesforce, HubSpot, and Billing share the same external_account_id or crm_account_id, When identity resolution runs, Then the records are merged into a single canonical customer automatically. Given two records share the same normalized primary domain and have no conflicting deterministic keys, When identity resolution runs, Then they are merged deterministically. Then a deterministic_merge event is emitted with source IDs and canonical_id. Then no duplicate customer with the same deterministic key remains in the search index. Then merge completes within 60 seconds of ingest for batches up to 10,000 records.
Fuzzy Match with Name and Email Domain Similarity
Given no deterministic match exists, When two accounts have Jaro–Winkler name similarity >= 0.92 and share at least one email domain, Then the system computes a confidence score >= 0.85 and proposes an auto-merge. When confidence >= 0.90 (auto-merge threshold), Then the system performs an auto-merge; otherwise, it places the pair in a review queue with rationale. Then regression evaluation on a labeled dataset shows <= 1% false positives and <= 5% false negatives for fuzzy matches at deployed thresholds. Then every fuzzy merge stores algorithm_version and confidence on the linkage.
Canonical Customer Creation with Stable Internal ID
Given any merge occurs, Then the system generates a canonical_id that is immutable, unique, and persists across resyncs and re-ingests. When upstream source records are updated or deleted, Then canonical_id remains unchanged and the linkage graph updates without breaking references. Then the canonical customer aggregates normalized fields (name, domains[], crm_account_id, billing_account_id[], lifecycle_stage, plan) and de-duplicates ARR by unique contract_id. Then GET /customers/{canonical_id} returns 200 with the aggregated view and last_updated timestamp.
Linkage Graph Traceability and Auditability
Given a canonical customer, When requesting GET /customers/{canonical_id}/links, Then the API returns all linked source records with edge_type in {deterministic, fuzzy, manual}, confidence (if applicable), timestamps, and actor/algorithm within 200 ms for up to 50 links. Then audit log includes every merge/unmerge event with actor, source IDs, reason (conflict summary or algorithm), ARR delta, and before/after snapshots. Then exporting the linkage graph allows reconstruction of the canonical state at any prior timestamp.
Manual Merge with ARR Deduplication Safeguards
Given a user selects two customers to merge, When their billing contracts overlap by contract_id or invoice_id, Then the resulting ARR is deduplicated so that overlapping contracts are counted once. When conflicting deterministic keys exist (different crm_account_id or external_account_id), Then the UI blocks bulk merge and requires explicit confirmation with a conflict summary. Then manual merge is idempotent and retriable; repeat submissions do not create duplicate links or double-count ARR. Then merge completion triggers re-enrichment and reindexing, and the UI shows status as Complete within 60 seconds.
Unmerge Rollback and Data Integrity
Given a previously merged canonical customer, When an unmerge is initiated, Then source records are restored to their prior linkage groups with original IDs and attributes. Then ARR totals are recalculated for resulting customers and match pre-merge values without inflation or loss. Then all audit and linkage entries are updated with an unmerge event including actor, rationale, and reversal details. Then the operation completes within 60 seconds and system state is consistent (no orphaned links).
Accurate Enrichment Attachment to Feedback
Given a feedback item contains reporter email or external account identifiers, When identity resolution completes, Then the feedback is linked to the correct canonical customer with >= 99% accuracy on a labeled validation set of at least 1,000 items. When a merge or unmerge changes the linked customer, Then the feedback’s enriched fields (ARR, plan, lifecycle_stage, renewal_date) update within 60 seconds and the activity log records the change. Then no feedback item remains unlinked when a deterministic key is present; exceptions are logged with error codes and retried up to 3 times.
Conflict Reconciliation Rules
"As a product manager, I want clear, configurable rules for resolving ARR and stage conflicts so that segment-aware scoring stays trustworthy."
Description

Introduces configurable, deterministic rules to reconcile conflicting values for ARR, plan, lifecycle stage, and renewal date from multiple sources. Applies source precedence, data freshness, and field-level confidence scoring to select the authoritative value, with full provenance stored for auditability. Allows workspace-level overrides and fallbacks when primary sources are missing or stale. Produces stable, trustworthy enrichment values that downstream scoring and reporting can rely on.

Acceptance Criteria
Resolve Conflicts by Precedence, Confidence, and Freshness
Given conflicting ARR, plan, lifecycle_stage, and renewal_date values exist across multiple sources And a workspace has configured source precedence and field-level confidence weights When reconciliation runs Then the authoritative value for each field is selected by highest precedence And if precedence ties, the value with higher field-level confidence is selected And if confidence ties, the value with the most recent source timestamp is selected And if timestamps tie, a deterministic tiebreaker (ascending source_id) is applied And identical inputs produce identical outputs across runs
Freshness Threshold and Fallback Policy
Given a configured stale_after threshold per field and per source And the top-precedence source value is older than its threshold When reconciliation runs Then the stale value is skipped and the next eligible source is evaluated And if no source meets freshness, the field is set to null with reason=stale and policy=fallback And an alert event reconciliation.stale_fallback is emitted with field, account_id, and skipped_sources
Workspace-Level Overrides with Expiry
Given a workspace-level override exists for account_id X on field=plan with value=P2 When new source data arrives with conflicting values Then the override value is selected regardless of source precedence And the selection records provenance rule=override with override_id and expiry_at (if set) And after expiry_at, the next reconciliation ignores the override and re-evaluates sources
Provenance Storage and Audit Retrieval
Given reconciliation selects authoritative values for an account When a client requests GET /enrichment/{account_id}/provenance Then the response includes authoritative_value, chosen_source, chosen_confidence, chosen_timestamp, applied_rule, and considered_sources[value, confidence, timestamp, precedence] for each field And each provenance entry includes an inputs_hash for auditability And any manual change or override appends an immutable audit record with actor_id, reason, timestamp, and previous_hash
Deduplication and Idempotent Reconciliation
Given multiple incoming records map to the same logical account via matching rules (crm_account_id, billing_customer_id, domain) When a batch containing duplicates is processed Then duplicates are collapsed into one logical entity before rule evaluation And repeated reconciliations with identical inputs result in zero changes to authoritative values and zero downstream events And conflicting duplicate inputs are captured in considered_sources with their source_ids
Downstream Stability and Change Events
Given downstream scoring and reporting consume authoritative enrichment values When an authoritative field value changes after reconciliation Then a single event enrichment.updated is emitted per changed field with previous_value, new_value, account_id, and provenance_rule And no event is emitted when the authoritative value remains unchanged And segment-aware scores are recomputed within one reconciliation cycle and reflected in the ranked queue and live theme map
Incremental Sync Scheduler and Recovery
"As a CX lead, I want data to stay current without manual refreshes so that decisions reflect the latest customer state."
Description

Provides a resilient scheduler that combines real-time event intake with periodic delta polling to keep enrichment data current. Handles pagination, checkpointing, and idempotent upserts, with exponential backoff, retries, and dead-letter queues for failures. Supports initial backfill, resumable sync after interruptions, and adaptive pacing under vendor rate limits. Ensures low-latency updates without manual refreshes and recovers gracefully from transient or systemic errors.

Acceptance Criteria
Real-Time Event Intake with Periodic Delta Polling
Given a new enrichment event with external_account_id and updated_at arrives, When it is ingested, Then the p95 time from ingestion to persisted update is <= 90 seconds and the record is visible to the scoring pipeline. Given no events are received for an account within a 10-minute window, When the 5-minute delta poll executes, Then all source records with updated_at > last_checkpoint are fetched and applied with max staleness p95 <= 10 minutes. Given an enrichment event and a delta poll update the same record within the same 5-minute window, When both are processed, Then exactly one upsert occurs and the version with the greatest updated_at wins.
Pagination and Checkpointed Resumability
Given a vendor API paginates with page_size=200 and next_cursor, When syncing 1,200 changed records, Then pages are requested until next_cursor is null and exactly 1,200 unique records are processed. Given a failure occurs after page 3 has been committed, When the sync resumes, Then processing restarts from page 4 without reprocessing pages 1–3. Given the vendor returns a duplicate page or overlapping items, When items are applied, Then no duplicate records are persisted and counts remain consistent with source change totals.
Idempotent Upserts and De-duplication
Given two updates for the same external_account_id with updated_at T1 and T0 (T1>T0), When they arrive in any order, Then the stored record reflects the T1 values and the T0 write is discarded as stale. Given the same payload is delivered more than once due to retries, When upserts execute, Then exactly one persistent change occurs and downstream notifications are emitted once. Given an incoming update lacks a stable unique key (e.g., external_account_id), When validation runs, Then the update is rejected with a logged error and no partial write is made.
Exponential Backoff, Retry, and Dead-Letter Handling
Given a transient 5xx or network timeout occurs on a poll request, When retries execute, Then up to 3 retries occur with exponential backoff starting at ~2s and doubling to a max delay <= 16s with jitter, before giving up. Given retries are exhausted for a message, When the final attempt fails, Then the item is moved to the DLQ with error code, request context, and first-seen timestamp, and an alert is emitted within 60 seconds. Given a transient error recovers during retry, When a subsequent retry succeeds, Then the item is not placed in DLQ and end-to-end latency for that item remains <= 5 minutes.
Adaptive Rate-Limit Pacing
Given the vendor returns HTTP 429 with a Retry-After header of N seconds, When pacing is applied, Then no further requests are sent to that vendor for at least N seconds and the next request occurs within N+5 seconds. Given X-RateLimit-Remaining reaches 0 with a reset time, When pacing is applied, Then requests are paused until the reset time and resume within 5 seconds after reset. Given rate-limits are encountered, When adaptive concurrency scaling is applied, Then the error rate for 429 responses falls below 1% within 10 minutes while sustaining at least 70% of pre-limit throughput.
Initial Backfill and Crash Recovery
Given an initial connection with no checkpoint and 1,000,000 source records, When backfill starts, Then records are processed in batches of <= 5,000 and checkpoints are persisted at least every 60 seconds or 10,000 records, whichever comes first. Given a process crash occurs mid-backfill after checkpoint K, When the service restarts, Then processing resumes within 60 seconds from checkpoint K with zero data loss and <= 1% reprocessing overhead. Given backfill is running while real-time events arrive for already-processed entities, When both streams execute, Then the final persisted state reflects all changes with latest-updated_at winning and no missed events.
Segment-Aware Score Enrichment
"As a product manager, I want theme scores to reflect revenue and renewal risk so that high-impact fixes rise to the top."
Description

Augments each feedback item and theme with ARR, plan, lifecycle stage, and renewal proximity, and recalculates theme and queue scores using configurable weighting models that emphasize revenue impact and renewal risk. Propagates updates to the ranked queue, live theme map, and one-click Jira/Linear sync payloads. Triggers automatic recomputation when upstream enrichment changes and maintains versioned score history for comparisons over time.

Acceptance Criteria
ARR/Plan/Lifecycle/Renewal Enrichment on Ingestion
Given a feedback item with a resolvable customer identifier arrives via any ingest channel, When ingestion completes, Then the item contains ARR, plan, lifecycle_stage, and renewal_proximity_days populated from upstream data within 60 seconds. Given the identifier cannot be resolved in any connected source, When ingestion completes, Then the item is marked enrichment_status='partial' and missing fields are null with an enrichment_error_code recorded. Given enrichment succeeds, Then field formats are enforced: ARR as decimal number, plan as string, lifecycle_stage as enum value from configured set, renewal_proximity_days as integer. Given 24 hours of operation, Then ≥99% of items with resolvable identifiers are fully enriched without manual intervention.
Conflict Resolution and De-duplication Across Sources
Given multiple connected sources provide a value for the same field, When values conflict, Then the system resolves using the configured source priority order with most-recent-updated as tie-breaker. Given a field value is resolved, Then enrichment_source and source_updated_at metadata are stored with the field for auditability. Given duplicated external messages/events map to the same feedback, When ingestion runs, Then only one enriched feedback item exists, determined by the configured deduplication key. Given a conflict is resolved, Then a reconciliation log entry is persisted and retrievable via API for at least 30 days.
Configurable Weighting Model for Revenue Impact and Renewal Risk
Given an admin updates the scoring model weights for revenue_impact and renewal_risk (and base signal weights), When the update is submitted, Then validation enforces allowed ranges and schema and rejects invalid configurations with a descriptive error. When a valid model is saved, Then item-, theme-, and queue-level scores are recalculated and committed within 2 minutes of save. Given any recalculation, Then model_version and the effective weight values are stored alongside each computed score and exposed via API/UI.
Automatic Recompute on Upstream Enrichment Change
Given ARR, plan, lifecycle_stage, or renewal data changes for an account in a connected source, When the change event is received or polled, Then scores for all associated feedback items and their aggregated themes are recomputed automatically and committed within 5 minutes. Given duplicate change events for the same source update, When recomputation is triggered, Then the operation is idempotent and does not create duplicate score versions. Given a recomputation attempt fails, When retries are exhausted, Then the affected records are flagged recompute_status='failed' with an alert emitted and an error entry recorded.
Propagation to Ranked Queue, Live Theme Map, and Sync Payloads
When scores are recalculated, Then the ranked queue order updates to reflect the latest scores and is consistent between UI and API within 30 seconds. When viewing the live theme map after a recomputation, Then node sizes/colors reflect updated theme scores within 30 seconds. When creating a Jira or Linear issue via one-click sync, Then the payload includes the current score, model_version, ARR, plan, lifecycle_stage, and renewal_proximity_days at the time of click.
Versioned Score History and Comparisons Over Time
Given any score recalculation, Then an immutable version record is created with timestamp, model_version, and identifiers for the enrichment snapshot used. When querying score history for a feedback item or theme, Then the latest 100 versions are retrievable via UI and API with their timestamps and scores. When requesting a comparison between two versions, Then the system returns differences in inputs (ARR, plan, lifecycle_stage, renewal_proximity_days) and weights within 2 seconds for the selected scope.
Data Quality Monitoring and Audit Trails
"As a data steward, I want visibility and alerts on enrichment quality so that I can quickly correct issues and maintain trust."
Description

Establishes continuous data quality checks for completeness, freshness, and anomaly detection on ARR and lifecycle fields, with thresholds and alerts to Slack and email. Provides dashboards and exportable logs showing value provenance, before/after changes, and user overrides with timestamps to satisfy audit and compliance needs. Enables fast diagnosis of mismatches and drift, reducing the risk of misleading scores or reports.

Acceptance Criteria
Completeness Monitoring and Alerting
Given the data quality service evaluates coverage every 15 minutes in UTC When coverage of ARR, plan, lifecycle_stage, or renewal_date across active accounts falls below global 99% or any segment falls below 97% Then post a Slack alert to the configured channel within 5 minutes containing metric, segment, current coverage, threshold, run_id, and a dashboard link And send an email alert to the configured list with a CSV of affected records (up to 10,000 rows) and counts by source system And suppress duplicate alerts for the same metric+segment for 2 hours
Freshness SLA Monitoring
Given freshness thresholds are set (ARR 60m, lifecycle_stage 4h, plan 4h, renewal_date 24h) and last_updated_at is tracked in UTC When more than 5% of records exceed the threshold for any field within a segment during a 15-minute evaluation window Then raise a Freshness Breach event and surface a red indicator on the dashboard for that field+segment And send a Slack alert including the 95th percentile staleness, threshold, segment, and top 5 lagging sources And create an audit log entry with run_id, field, segment, thresholds, counts, and timestamp
Anomaly Detection on Value Distribution
Given a 14-day rolling baseline and sensitivity of 3 standard deviations When the mean or median ARR by segment shifts beyond 3σ from baseline or the null rate increases by ≥50% within 1 hour Then tag the segment with Anomaly Detected and open an anomaly detail view showing affected cohorts And notify via Slack and email with baseline vs current stats and a sparkline And persist an anomaly record with resolution status (open, muted, resolved) and owner defaulting to data-stewards
Audit Trail: Before/After, Provenance, and Export
Given any change to ARR, plan, lifecycle_stage, or renewal_date is applied by sync or user override When the change is committed Then record an immutable audit entry including entity_id, field, before_value, after_value, source_system, source_record_id, rule_applied, confidence_score, actor (system|user_id), job_id, and ISO-8601 UTC timestamp And make entries queryable by entity_id, field, date range, source_system, and actor returning first page within 2 seconds for 100k entries And allow export of filtered logs to CSV and JSON with a checksum, completing within 30 seconds for up to 1,000,000 rows And retain logs for at least 365 days with access controls honoring workspace roles
User Overrides with Safeguards and Reconciliation
Given a user with Editor role manually overrides a monitored field in EchoLens When the override is saved with a mandatory justification of at least 10 characters Then reflect the override in product views within 5 seconds and mark user_override=true in the audit trail And prevent background sync from overwriting the value for 30 days unless the user clears the override or confirms a forced refresh And if the source-of-truth value diverges by ≥10% ARR or lifecycle_stage changes, create a review task and notify the override author
Conflict Resolution and Confidence Scoring
Given conflicting values for the same field arrive from multiple connectors in one sync run When the reconciliation policy is evaluated (default priority Salesforce > Billing > HubSpot; tie-breaker by latest source timestamp) Then select the resolved value per policy, compute a 0–1 confidence score, and store all raw source values with timestamps in provenance And display the policy rationale and confidence in the UI and audit trail And if confidence < 0.70 or sources disagree by ≥20% ARR, flag the record for steward review and include it in the daily exceptions report
Quality Dashboard and Trends
Given a workspace with at least 10,000 accounts When the Data Quality dashboard is loaded Then show KPIs for completeness, freshness SLA adherence, anomaly count (last 7 and 30 days), and exceptions by segment with filters for field, source, segment, and date range And render within 2 seconds on warm cache and within 5 seconds on cold cache with data freshness no older than 5 minutes And provide deep links to affected records and their audit trails
Self-Serve Field Mapping and Overrides UI
"As a workspace admin, I want to configure field mappings and overrides myself so that ARR Sync works with our custom schemas."
Description

Delivers an admin UI for configuring field mappings from Salesforce/HubSpot and billing schemas to EchoLens canonical attributes, setting source precedence and fallbacks, and testing connections. Displays sync status, last run, and error diagnostics, and allows scoped, reversible overrides of ARR, plan, stage, and renewal values with optional expiry. Enforces role-based access and audit logging to reduce support load and enable safe self-service.

Acceptance Criteria
Admin maps CRM and billing fields to EchoLens attributes
Given an Admin is on the Field Mapping page, when they select a connected source and choose fields for ARR, Plan, Lifecycle Stage, and Renewal Date, then the UI validates field existence and data types before enabling Save. Given any selected field has an incompatible type or missing permission, when Save is clicked, then the save is blocked and inline errors identify each offending field. Given all selected fields are valid, when Save is clicked, then the mapping is saved and versioned, a success message is shown, and the configuration persists across page reloads. Given a saved mapping, when the next enrichment job runs, then the job uses the latest saved mapping version.
Configure source precedence and fallback resolution
Given at least two sources are mapped to the same attribute, when the Admin orders them by precedence, then the UI enforces a strict total order with no ties and persists the order after Save. Given conflicting non-empty values across sources for an attribute, when enrichment runs, then the highest-precedence non-empty value is selected and the chosen source is recorded for diagnostics. Given the top-precedence value is null/empty/blank, when enrichment runs, then the system falls back to the next source until a non-empty value is found, otherwise the attribute remains unset. Given duplicate records for the same account across sources, when enrichment runs, then records are de-duplicated by the canonical account key before precedence is applied.
Test and validate connections
Given a connected integration (Salesforce, HubSpot, or billing), when the Admin clicks Test Connection, then the system attempts authorization and a minimal read and returns Pass or Fail within 10 seconds. Given credentials are revoked or expired, when Test Connection is run, then the result is Fail with a clear remediation message indicating the action required to restore connectivity. Given a mapping is saved, when Validate Mapping is run, then the system verifies referenced fields exist and match expected types and returns Pass with counts or Fail with per-field errors.
Display sync status, last run, and error diagnostics
Given enrichment jobs are running, when the Admin opens the Status panel, then the UI shows the current state (Idle, Running, Failed), last run timestamp in ISO 8601 UTC, and last run duration. Given the last run completed, when viewing diagnostics, then the UI displays counts for processed records, updates applied, conflicts resolved, de-duplicates performed, and errors encountered. Given errors occurred in the last run, when the Admin expands Error Details, then the UI lists recent errors with source, record identifier, error code, and message, and supports downloading a CSV of errors.
Create scoped override with optional expiry
Given an Admin opens the Overrides page, when they create a new override and select an attribute (ARR, Plan, Lifecycle Stage, or Renewal Date), then the UI requires at least one scope filter (e.g., Account ID list, domain, or plan) before enabling Save. Given a valid new value and optional expiration date/time are entered, when the override is saved, then the override becomes Active immediately and is applied to affected records within 2 minutes. Given an override has an expiration, when the expiration time is reached, then the override automatically transitions to Expired and source-resolved values are restored without manual action.
Revert overrides safely with full traceability
Given an Active override exists, when an Admin clicks Revert and confirms with a reason, then the override stops applying immediately and affected records reflect source-resolved values within 2 minutes. Given a revert occurs, when viewing the override details, then the audit shows before/after values, actor, timestamp, reason, and scope, and the override status is Reverted. Given a Reverted or Expired override, when the Admin clicks Restore, then the override returns to Active with its original scope and value.
Role-based access control and audit logging
Given a user without Admin privileges attempts to access the Mapping & Overrides UI, when the page loads, then access is denied with a 403 message and no configuration data is exposed. Given an Admin performs any create, update, or delete action on mappings, precedence, tests, or overrides, when the action completes, then an immutable audit entry is recorded with user, timestamp, entity, old value, new value, and outcome. Given audit entries exist, when an Admin filters by user, entity type, or date range, then the audit list returns matching entries accurately and entries cannot be edited or deleted via the UI.

Correlation Hints

Learns which segments (e.g., plan, cohort, region) correlate with churn, low NPS, or blocked deals and suggests weight adjustments with plain-language rationale. Helps teams tune scoring based on real business impact, not guesswork.

Requirements

Segment Catalog & Mapping
"As a PM/CX lead, I want to define and map business segments from my data sources so that Correlation Hints can analyze outcomes by the segments that matter to my company."
Description

Introduce a configurable catalog that defines business segments (e.g., plan tier, signup cohort, customer region, industry, account size) and maps them to user/account records from connected sources. Provide UI and APIs to create, edit, and validate segment definitions, including rules-based mappings, lookups, and derived attributes (e.g., cohort by first_seen month). Support schema detection, null-handling, and sample-size calculations per segment over a selectable time window. Persist segment lineage and versioning to ensure reproducibility of historical Correlation Hints. Integrate with EchoLens ingest pipeline so segments are available to the correlation engine and the live theme map without duplicating data transformations.

Acceptance Criteria
Create and Validate Segment via UI (Rules, Lookups, Derived Cohort)
Given a connected Users dataset with fields first_seen_at and plan_id and a Plans lookup mapping plan_id->plan_tier, When a PM creates a segment "Plan Tier" using a lookup on plan_id and a derived attribute "Signup Cohort (YYYY-MM)" = date_trunc('month', first_seen_at), Then the expression and lookup validate with no errors and the preview renders within 3 seconds. Given the PM references a non-existent field or invalid function, When they click Validate, Then an inline error shows the offending token and 1-based position, and Save is disabled. Given first_seen_at is null for some records, When previewing "Signup Cohort (YYYY-MM)", Then those records are bucketed as "Unknown" if the "Treat null as Unknown" toggle is on, or excluded if off, and the preview distribution updates accordingly. Given a valid configuration, When the PM clicks Save, Then the segment is persisted with version v1, status Active, creator, created_at timestamp, and a unique segment_id.
Schema Detection and Null Handling Preview
Given a new data source has completed ingestion in the last 24 hours, When the user opens Add Mapping Rule, Then schema detection lists all available fields with inferred types and last_detected_at timestamp. Given fields with nulls exist, When the user views the field details, Then the UI shows null rate (percentage and count) computed over the selected time window. Given the user toggles "Treat null as Unknown", When they save the rule, Then the setting is persisted and reflected in both preview and the API GET /segments/{id} response.
Mapping Coverage and Sample-Size Calculation by Time Window
Given a selectable time window control, When the user sets the window to Last 30 days, Then for each segment the system displays sample size (distinct users and distinct accounts) and coverage % within 5 seconds for up to 50 segments. Given sample size for any segment is below 30, When metrics are displayed, Then a Low Sample flag appears next to the segment and the API returns "low_sample": true for that segment in GET /segments/metrics. Given the user exports metrics, When they click Export CSV, Then the file downloads with columns: segment_id, version_id, window_start, window_end, users, accounts, coverage_pct, low_sample.
Segment Lineage, Versioning, and Historical Reproducibility
Given a segment v1 exists, When the definition is edited (e.g., a rule change) and saved, Then v2 is created with a new immutable version_id, diff summary, editor, and created_at while v1 remains retrievable. Given a Correlation Hints job is run for 2025-06-01 to 2025-06-30, When segment versions have changed after that period, Then the job uses the segment version effective within the analysis window and records the version_id in job audit. Given a fixed data snapshot and the same job parameters, When the job is re-run, Then segment assignments and counts match 100% (checksum equality) with the earlier run. Given lineage view is requested, When the user opens Segment Details > Lineage, Then the UI lists source fields, lookup tables, derived expressions, and upstream source connection IDs.
Segments Available to Correlation Engine and Live Theme Map
Given a segment is Active, When new reviews, emails, tickets, or chats are ingested, Then segment assignments are computed via the shared transformation graph (no duplicate pipelines) and attached to events within 10 minutes (p95) of ingestion. Given segment assignments exist, When the correlation engine runs, Then segments are present in the feature matrix and runs do not fail due to missing segment columns. Given the live theme map is open, When segment assignments update, Then the map's segment filters and counts reflect the new assignments within 60 seconds of computation completion. Given API GET /events/{id} is called, When the event has mappable segments, Then the response includes segment values and version_id for each segment.
Segment Catalog API Endpoints and AuthZ
Given an API token with scope segments:write, When calling POST /segments with a valid payload, Then the API returns 201 with segment_id, version_id, and the persisted definition matching the request. Given an API token without the required scope, When calling POST /segments or PATCH /segments/{id}, Then the API returns 403 with error code "forbidden". Given a malformed request, When calling POST /segments, Then the API returns 422 with machine-readable validation codes (e.g., "unknown_field", "invalid_expression") and a pointer to the field path. Given a repeated POST with the same Idempotency-Key header within 24 hours, When the original request succeeded, Then the API returns 201/200 with the original resource and no duplicate segment is created. Given rate limits of 60 write requests per minute per account, When the client exceeds the limit, Then the API returns 429 with a Retry-After header.
Outcome Metrics Ingestion & Linking
"As a PM, I want EchoLens to ingest churn, NPS, and deal-block data and link it to accounts and feedback so that correlations reflect real business outcomes."
Description

Add connectors and schemas to ingest churn status and dates (billing/CRM), NPS responses (survey tools), and deal block reasons/stages (CRM), with configurable freshness SLAs. Implement identity resolution to link outcomes to accounts/users and their feedback (reviews, tickets, chats) using stable keys and fallbacks (email, account_id, external_id). Normalize and deduplicate events, compute latest outcome state per entity, and maintain time windows for pre/post outcome analysis. Expose a unified outcome fact table accessible to the correlation engine and ranking model. Ensure backfill support and incremental updates so Correlation Hints reflect current business impact.

Acceptance Criteria
Multi-Source Outcome Ingestion (Churn, NPS, Deal Blocks)
Given valid connectors for billing/CRM and survey tools are configured with credentials and source mappings When an initial backfill is triggered with a 180-day lookback Then churn statuses/dates, NPS responses, and deal-block reasons/stages are fetched and persisted to raw_outcomes staging with source record IDs and timestamps When incremental sync runs on schedule using source watermarks/cursors Then only new or updated records since the last successful sync are appended to raw_outcomes Then failed fetches are retried up to 3 times and surfaced as error events with source and reason
Identity Resolution Linking Outcomes to Accounts/Users and Feedback
Given normalized outcome events and feedback events exist When identity resolution executes Then each outcome event is assigned a canonical entity_pk using precedence: stable_key > external_id > account_id/user_id > email Then outcomes are linked to feedback via entity_pk, enabling many-to-one associations Then events without a resolvable entity_pk are quarantined in unresolved_identity with reason and source_id
Normalization, Deduplication, and Event Ordering
Given raw outcome events from multiple sources with varying schemas When the normalization job runs Then events are mapped to the canonical schema {entity_type, entity_pk, outcome_type, status/value, reason, stage, score, occurred_at_utc, source, source_id, inserted_at_utc} Then duplicate events are removed using composite key (source, source_id) or hash(entity_pk, outcome_type, occurred_at_utc, status/value) Then occurred_at is converted to UTC and ordering keys are computed (occurred_at_utc, inserted_at_utc)
Latest Outcome State per Entity
Given normalized, ordered outcome events When the latest-state computation runs Then for each entity_pk and outcome_type the most recent state is stored in outcome_latest_state with occurred_at_utc and source provenance Then churn state reflects the latest status and churn_date if applicable Then NPS state reflects the latest valid response per respondent within the defined survey cycle Then deal-block state reflects the most recent block reason and stage
Pre/Post Outcome Analysis Windows
Given a configurable window definition per outcome_type (e.g., pre_days, post_days) When windows are materialized Then for each outcome event, window_start_utc and window_end_utc are computed based on occurred_at_utc and configuration Then feedback events linked by entity_pk and whose created_at_utc falls within the window are associated to that outcome event Then boundary conditions are deterministic: start inclusive, end exclusive
Freshness SLA Enforcement and Alerts
Given a freshness SLA per source defined in hours When current_time_utc - last_successful_sync_utc exceeds the source SLA Then the source is marked stale and a freshness_alert is emitted with source, last_successful_sync_utc, and SLA values Then stale sources are excluded from correlation calculations until freshness is restored or an override flag is set
Unified Outcome Fact Table Exposure and Engine Access
Given normalized outcomes, resolved identities, latest states, and windows exist When the outcome_facts table/view is published Then it exposes the columns {entity_type, entity_pk, outcome_type, status/value, reason, stage, score, occurred_at_utc, latest_state_flag, window_id, window_type, source, source_id} Then the correlation engine and ranking model can query outcome_facts via the internal API and warehouse, validated by a smoke query that returns expected rows for a known test entity within 5 minutes of ingestion Then outcome_facts is updated incrementally after each sync cycle and reflects backfills within one run
Correlation Modeling Engine
"As a data-informed PM, I want statistically sound correlation scores with confidence levels so that I can trust which segments drive negative outcomes."
Description

Build a statistical engine that quantifies relationships between segments and target outcomes (churn, low NPS thresholds, blocked deals) with effect sizes and confidence scores. Support categorical and continuous predictors, apply appropriate tests/models (e.g., chi-square, Fisher’s exact, logistic regression), and include sample-size thresholds and multiple-hypothesis correction to reduce false positives. Provide optional adjustment for common confounders (e.g., plan tier and region) via multivariate modeling. Output structured results including segment definition, effect size, confidence interval, p-value, minimum detectable effect checks, and data quality flags. Cache results by time window and trigger re-computation on new data to feed Correlation Hints and the live theme map.

Acceptance Criteria
Auto-selects statistical test per predictor type and sample size
Given outcome_type=binary and predictor_type=categorical with all expected cell counts >= 5 When the engine computes the association Then method='chi_square' is used and p_value is returned Given any expected cell count < 5 for a 2x2 table When the engine computes the association Then method='fishers_exact' is used and p_value is returned Given outcome_type=binary and predictor_type=continuous When the engine computes the association Then method='logistic_regression_univariate' is used and outputs coefficient, odds_ratio_per_sd, ci_lower_95, ci_upper_95, and p_value Then each result includes method equal to the statistical test used
Controls false discoveries across segment tests
Given M >= 2 segment-outcome tests within the same compute batch and time_window When results are generated Then Benjamini-Hochberg FDR correction with q=0.05 is applied and q_value is populated for each test Then significance_flag is true only if q_value <= 0.05 Then each result includes raw p_value and corrected q_value and confidence_score in [0,1]
Enforces sample size and MDE thresholds
Given configuration min_total_n=200, min_group_n=20, alpha=0.05, power=0.8 When a segment's association is evaluated Then if n_total < 200 or any group_n < 20, data_quality_flags includes 'low_sample' and excluded_from_hints=true Given baseline_rate p0 computed from the reference group When minimum detectable effect (mde) is calculated at alpha=0.05 and power=0.8 Then mde is included in the result Given observed absolute effect_size < mde When reporting the result Then data_quality_flags includes 'underpowered'
Supports multivariate adjustment for confounders
Given adjust=true and confounders subset_of(['plan_tier','region']) are available When the engine computes associations Then method='logistic_regression_multivariate' is used and adjusted_effect_size (adjusted_odds_ratio), ci_95, and p_value_adjusted are returned alongside unadjusted metrics Given any confounder with variance_inflation_factor (VIF) > 5 When fitting the multivariate model Then the confounder is dropped, data_quality_flags includes 'collinearity', and dropped_variables lists the removed confounders with their VIF values
Outputs complete, structured correlation results
Given a completed computation for a segment-outcome pair When emitting results Then the JSON includes: segment_definition, outcome, effect_size_type, effect_size_value, ci_lower_95, ci_upper_95, p_value, q_value (if corrected), confidence_score, method, n_total, n_groups, group_breakdown (counts and outcome_rates per level), mde, data_quality_flags[], computation_time_ms, model_version, time_window {start_at_iso, end_at_iso}, computed_at_iso Then all numeric fields are within valid ranges (0 <= p_value <= 1, ci_lower_95 <= ci_upper_95, 0 <= confidence_score <= 1) Then the payload validates against schema version 'correlation_result.v1' with zero validation errors
Caches by time window and re-computes on new data
Given identical parameters and no new ingested data affecting the time_window When the engine is called within cache_ttl=15m of the last compute Then previous results are returned with cache_hit=true and computation_skipped=true and results identical to the prior payload Given new data that changes any group counts within the time_window When the engine receives the data Then the corresponding cache entry is invalidated and recomputation occurs within 60s, producing cache_hit=false and computed_at_iso > previous computed_at_iso Then updated results are published to downstream consumers (Correlation Hints and live theme map) within 120s and an audit log entry with timestamp and compute_id is recorded
Weight Recommendation & Simulation
"As a PM, I want suggested weight changes with a preview of how they will reorder my queue so that I can tune scoring based on business impact without guesswork."
Description

Translate correlation findings into suggested weight adjustments for EchoLens’ theme scoring model, with guardrails (caps, rate limits) and rationale codes. Generate per-theme delta weights based on effect size and business impact heuristics, respecting existing manual overrides. Provide an interactive simulation that previews before/after priority queues, theme ranks, and expected impact on key KPIs without committing changes. Support scheduled application, time-bound trials, and automatic reversion. Expose an API and event hooks so accepted changes propagate to Jira/Linear sync and downstream workflows.

Acceptance Criteria
Generate Guarded Weight Recommendations from Correlations
Given fresh correlation findings for relevant segments are available and the scoring model has configured bounds When the recommendation job runs Then the system generates per-theme delta_weight suggestions proportional to effect size and business-impact heuristics And each suggestion includes rationale_code, human_explanation, and confidence (0–1) And delta_weight respects max_delta_per_apply (default 0.15) and keeps resulting weight within [min_weight, max_weight] (defaults 0.0–1.0) And no more than max_adjustments_per_24h (default 1) are produced per theme And themes with sample_size < min_samples (default 50) or confidence < min_confidence (default 0.7) are excluded And the job completes within 60 seconds for up to 5,000 themes
Honor Manual Overrides in Recommendations and Apply
Given a theme has an active manual weight override or lock_until When generating suggestions or applying scheduled changes Then the system marks the suggestion as constrained_by_override and sets delta_weight = 0 unless allow_override = true is explicitly set by an admin And simulation displays a lock badge and shows the hypothetical outcome if the override were removed And no applied change alters locked weights And all such decisions are recorded in the audit trail
Interactive Simulation of Queue and KPI Impact (No Commit)
Given a user selects one or more proposed changes When the simulation is opened Then the UI shows side-by-side before/after theme ranks, queue deltas, and expected impact on KPIs (e.g., NPS, churn risk, blocked deals) with 80% confidence intervals And no production weights are changed until the user chooses Apply or Schedule And simulation for up to 2,000 themes renders within 2 seconds (p50) and 5 seconds (p95) And toggling any suggestion on/off updates the preview within 500 ms (p95)
Scheduled Apply, Time‑Bound Trial, and Auto Reversion
Given an authorized user chooses Apply with a schedule or Start trial When a future start_at and optional trial_duration are configured Then the system validates conflicts, stores a signed change set with checksum, and queues execution in the tenant timezone And at start_at the change set applies atomically And if trial_duration is set, the system automatically reverts to the prior weights at end_at and posts a summary to the activity feed and notifications And users can cancel pending schedules or end trials early with one click And each operation completes within 10 seconds end-to-end and failures auto-retry 3 times with exponential backoff
Rationale Codes, Explanations, and Audit Log
Given recommendations are generated or applied When viewing details or exporting logs Then each theme change shows rationale_code (e.g., COHORT_CHURN_UPLIFT, REGION_NPS_DROP), human_explanation, inputs (segments, effect_size, sample_size), and guardrail flags And an immutable audit entry captures actor, timestamp, before_weight, delta_weight, after_weight, source_job_id, and correlation_snapshot_id And audit entries are filterable by date range, actor, rationale_code and exportable as CSV and JSON
API Endpoints and Event Hooks for Downstream Sync
Given an API consumer with valid credentials When calling GET /recommendations, POST /simulate, POST /changesets (apply/schedule), and GET /weights Then responses conform to the published schema with typed fields, rationale codes, and guardrail metadata And webhooks emit events (recommendations.ready, changeset.applied, changeset.reverted) within 30 seconds of state change with at-least-once delivery and idempotency keys And accepted changes propagate updated priority queues to Jira/Linear sync within 60 seconds, reflecting new ranks in created/updated tickets And all endpoints meet p95 latency ≤ 800 ms and 99.9% availability SLOs
Data Quality Thresholds and Safety Constraints
Given incoming data may be delayed, sparse, or anomalous When correlation freshness exceeds max_correlation_age (default 7 days) or validation fails Then the system suppresses recommendations and surfaces a data_stale warning with remediation hints And extreme outliers are winsorized per configuration, and negative-impact suggestions beyond risk_threshold are quarantined for manual review And a cooling_period (default 24h) prevents oscillating adjustments on the same theme
Plain-Language Rationale Generator
"As a stakeholder, I want clear, plain-language explanations for each suggestion so that I can quickly understand and communicate why a change is recommended."
Description

Create a natural-language layer that converts statistical outputs and business rules into concise explanations for each hint (e.g., sample size, effect magnitude, time window, adjustments, caveats). Include links to underlying data slices and charts for verification. Localize terminology to non-technical audiences while preserving accuracy, and highlight data quality limitations when thresholds are not met. Ensure consistency in tone, formatting, and variable substitution. Surface these rationales across the hints panel, notifications, and exported reports.

Acceptance Criteria
Hint Panel Rationale Rendering
Given a correlation hint is generated for a segment and metric with effect size, sample size, time window, and suggested weight adjustment When the hints panel renders the rationale Then the text includes the segment, metric, direction and magnitude, sample size (n), time window, and the suggested adjustment And all placeholders are resolved with actual values (no "{...}" tokens) And the text is 1–3 sentences, 60–350 characters, sentence case, and neutral tone And numeric values are rounded (percent to 1 decimal, counts to 0 decimals) and formatted per user locale
Links to Data Slices and Charts
Given a rationale is shown with "View data slice" and "View chart" links tied to the hint's filters When a user clicks "View data slice" Then the opened view applies the same segment, metric, time window, and sample size shown in the rationale and returns matching counts within ±1 And the URL includes shareable filters and loads without error When a user clicks "View chart" Then the chart displays the metric trend for the segment over the time window with the effect magnitude annotated And the links are present in panel, notifications (where supported), and exports
Statistical Evidence Translation
Given effect size, confidence, sample size, and timeframe are available When generating the rationale Then avoid technical jargon (e.g., "p-value", "coefficient", "CI"); use plain terms (e.g., "confidence", "impact") And include effect direction and magnitude with confidence (e.g., "95% confidence") and sample size (e.g., "n=512") And state the timeframe in natural language (e.g., "last 30 days") And apply rounding rules (percent to 1 decimal, confidence to 0 decimals) And produce deterministic output for identical inputs
Data Quality Threshold Warnings
Given data quality thresholds (min sample n >= 50, missing rate <= 10%, recency <= 30 days) are not met for a hint When generating the rationale Then prepend a limitation phrase starting with "Data is limited:" and explain which thresholds failed And suppress the weight adjustment or tag as "Do not adjust" when confidence < 80% or n < 50 And include a link to the data quality view And maintain the same tone and format as standard rationales
Localization for Non-Technical Audiences
Given the organization language is set to English (US) and the audience is non-technical When generating the rationale Then use glossary mappings for non-technical terms and expand uncommon acronyms on first use (e.g., "Net Promoter Score (NPS)") And keep Flesch–Kincaid grade <= 10 and length <= 350 characters And format dates, numbers, and percents per locale (e.g., 1,234; 12.5%) And avoid more than one hedging qualifier per sentence
Consistency Across Surfaces
Given a single hint exists When viewed in the hints panel, as an in-app notification, and in an exported report (PDF/CSV) Then the rationale message content is identical across surfaces (except link render style) and variables match the same values And links are active where supported; in PDF, URLs appear as footnotes; in CSV, link columns are populated And timestamps and numeric formats are consistent across surfaces And snapshot tests verify the text for all surfaces
Hint Delivery UI & Workflow
"As a PM, I want an in-app hints panel where I can review, filter, and apply suggestions so that I can act without leaving my workflow."
Description

Add a dedicated Correlation Hints panel in EchoLens with filters by segment, outcome, time window, and confidence score. Provide list and detail views showing rationale, affected themes, simulated impact, and apply/reject actions with bulk operations. Integrate notifications and badges for new or updated hints, and surface inline prompts on the live theme map. Record user actions and comments to support collaboration and later audits. Ensure responsive performance and accessibility across desktop and mobile.

Acceptance Criteria
Correlation Hints Panel Filtering and Persistence
Given I open the Correlation Hints panel, When I set filters for segment, outcome, time window, and confidence score, Then only matching hints are shown and filter chips reflect the current values. Given I change any filter, When the query executes, Then the list refresh completes within 2s p95 and 500ms p50 for datasets up to 5k hints. Given I apply a time window preset (7/30/90 days) or a custom range, When results load, Then the applied window is displayed in the header and uses the workspace timezone consistently. Given I adjust the confidence slider in 1% increments, When I apply it, Then the list updates and the URL query string reflects the filters for shareability. Given I navigate away and return within the same session, When the panel reopens, Then my last-used filters persist until I click Reset, which restores the default filter set.
Hints List View: Sorting, Selection, and Empty States
Given the Correlation Hints list is loaded, When it renders, Then each row displays title, segments, outcome, confidence (%), impacted themes count, last updated timestamp, and status. Given the list is visible, When I sort by confidence or last updated or impacted themes, Then the list reorders correctly and the active sort is indicated in the column header. Given there are more than 50 hints, When I paginate or infinite scroll, Then the next page loads within 1.5s p95 and the scroll position is preserved. Given checkboxes are available, When I select rows, Then the bulk action bar appears with the correct selection count; When I use Select all on page or Select all matching filters, Then selection reflects the intended scope. Given no hints match current filters, When the list loads, Then an empty state message appears with a Clear filters action that restores defaults.
Hint Detail View: Rationale, Affected Themes, and Simulated Impact
Given I open a hint detail, When it loads, Then I see plain-language rationale, targeted segments, outcome metric (e.g., churn/NPS/deal status), confidence score, sample size, and last updated timestamp. Given affected themes exist, When I view the Affected themes section, Then each theme shows current rank, projected rank delta if applied, and a link to the theme detail. Given a simulation panel is present, When I adjust the proposed weight using a slider or preset, Then projected impacts recompute within 500ms p95 for the top 20 themes without page reload. Given data freshness matters, When the detail is shown, Then it indicates the dataset timestamp used for the simulation and disables Apply if data is older than the workspace freshness threshold. Given action controls are present, When I view the detail, Then Apply and Reject buttons are available with their current state (e.g., already applied/rejected) clearly indicated.
Apply/Reject Single Hint with Comment and Audit Trail
Given a hint is selectable, When I click Apply, Then a confirmation modal shows the summary of changes and an optional comment field. Given I confirm Apply, When the operation completes, Then the hint status updates to Applied within 2s p95, the simulated impact is reflected in the queue, and a success toast appears. Given a hint is selectable, When I click Reject and confirm with an optional comment, Then the hint status updates to Rejected within 2s p95 and is excluded from future Apply operations unless updated. Given an action is taken, When the system logs it, Then an immutable audit entry is created with hintId, action, userId, ISO 8601 timestamp, previous and new weights (if applicable), affected theme IDs, and comment. Given audit entries exist, When I open the Activity tab in the hint detail, Then I can view the chronological log and filter by action type (Applied/Rejected/Updated/Commented).
Bulk Apply/Reject with Conflict Handling and Partial Success
Given I have 1–100 hints selected, When I choose Bulk Apply or Bulk Reject, Then a summary modal shows the count, optional shared comment, and any pre-detected conflicts. Given I confirm the bulk action, When processing runs, Then a progress indicator displays and the operation completes within 5s p95 for 100 hints. Given some hints changed since selection, When the bulk action executes, Then stale or already-applied/rejected hints are skipped with specific reasons and do not block other items. Given the operation completes, When I view the result, Then I see counts for succeeded, skipped, and failed items and can download a CSV report of per-hint outcomes. Given auditability is required, When a bulk action is performed, Then an audit entry is written for each hint and includes a bulkOperationId linking all entries and the shared comment.
Visibility: Notifications, Badges, and Live Theme Map Prompts
Given new or updated hints are generated, When I am in the app, Then an in-app notification appears within 10s and a badge count displays on the Correlation Hints nav item and panel tab. Given I click a notification, When it opens, Then I am deep-linked to the relevant filtered list or specific hint detail and the badge count decreases for viewed items. Given the live theme map is open, When a theme has an associated high-confidence hint, Then an inline prompt icon appears on the node with a tooltip summary; clicking it opens the hint detail. Given potential clutter, When prompts render on the map, Then no more than 3 prompts show per viewport and a Show all prompts control becomes available if more exist. Given read state matters, When I mark notifications as read or open the related hint, Then the read/unread state persists across sessions and is reflected in badge counts.
Performance, Responsiveness, and Accessibility
Given I access the Correlation Hints panel on desktop (≥1280px) or mobile (≥375px), When the UI renders, Then there is no horizontal scroll and primary actions are reachable within two taps/clicks. Given a cold load on a fast 3G network, When I open the Correlation Hints panel, Then time-to-interactive is ≤3s p95 and subsequent navigations are ≤1.5s p95. Given I navigate with a keyboard, When I tab through the interface, Then all interactive elements are focusable in a logical order with visible focus indicators and operable without a mouse. Given accessibility requirements, When I use a screen reader, Then control labels/roles are announced via ARIA, live regions announce apply/reject success, and color contrast meets WCAG 2.1 AA. Given device rotation, When I switch between portrait and landscape on mobile, Then layouts reflow correctly and preserve state without content loss.
Governance, Audit, and Rollback
"As an admin, I want approvals, audit trails, and rollback for weight changes so that we maintain control and can trace impact."
Description

Implement role-based access controls for viewing, approving, and applying weight changes, with optional multi-step approvals. Maintain a versioned history of scoring model changes including who, when, what changed, rationale, and linked hints. Provide one-click rollback and comparison of pre/post metrics to assess impact, plus optional A/B or holdout tests. Add safeguards against oscillation by rate-limiting adjustments and alerting on anomalous change patterns. Expose audit exports and webhooks for compliance and analytics.

Acceptance Criteria
RBAC: View, Approve, and Apply Permissions
Given a user with role "Viewer" When they open the Weight Adjustments UI Then they can view proposed changes but no Approve or Apply controls are visible Given a user with role "Viewer" When they call POST /scoring/weights/apply Then the response is 403 and an audit event "unauthorized_apply_attempt" is recorded with userId and timestamp Given a user with role "Approver" When they approve a change with status "Pending" Then the change transitions to "Approved" and the approval is recorded with userId and timestamp Given a user with role "Approver" When they call POST /scoring/weights/apply Then the response is 403 Given a user with role "Applier" And a change with status "Approved" When they call POST /scoring/weights/apply with that changeId Then the response is 200 and a new version is created Given a user with role "Applier" And a change with status "Pending" When they call POST /scoring/weights/apply Then the response is 409 and no version is created Given any user When any RBAC check fails Then a 403 is returned and the attempt is logged to the audit trail with action, userId, resource, and outcome
Multi-step Approval Policy Enforcement
Given a policy approvals_required=2 and risk_threshold=15% When a proposed change has aggregate weight delta > 15% Then the change status is "Pending (High Risk)" and requires two distinct approvers Given a change requiring 2 approvals When the proposer attempts to approve their own change Then the response is 409 and no approval is recorded Given a change requiring 2 approvals When two distinct users with role "Approver" approve it Then the status transitions to "Approved" and both approvals are recorded Given an approval older than 7 days When the second approval is attempted Then the first approval is expired and must be re-submitted Given a policy approvals_required=1 When a low-risk change (<= 15%) is proposed Then exactly one approval is sufficient to reach status "Approved"
Versioned History and Immutable Audit Trail
Given an approved apply action When it is executed Then a new scoring_model_version is created with versionId, appliedBy, appliedAt, rationale (non-empty), and linked hintIds Given a new version When persisted Then the full diff of weights (before->after) is stored and is retrievable via GET /scoring/versions/{versionId} Given any existing version When a user attempts to modify its stored fields Then the operation is rejected with 409 and an audit event "immutable_version_violation" is recorded Given a request GET /scoring/versions?page=1&pageSize=50 Then results are returned in reverse chronological order with totalCount and are paginated Given the audit log When queried by date range and actor Then entries include who, when, action (propose/approve/apply/rollback), what changed, rationale, and linked hints
One-click Rollback with Pre/Post Metric Comparison
Given an existing version Vn When a user with role "Applier" clicks Rollback to version Vn Then the active weights revert atomically to Vn and a new version Vn+1 is created with reason "rollback" and linkTo=Vn Given a rollback execution When completed Then the response includes success=true and executionTimeMs and the audit trail records rollback_applied with userId and timestamps Given a rollback is initiated Then a comparison view is generated showing pre vs post metrics (e.g., churn_rate, NPS, blocked_deals_rate) over a configurable window (default 14 days) with absolute and percent deltas Given the comparison window has sample size below the configured minimum Nmin Then the view displays "insufficient_data" and no pass/fail badge is shown Given an A/B test is active on the current model When a rollback is requested Then the user is prompted to stop or exclude the test; proceeding records the decision in the audit log
A/B and Holdout Testing of Weight Changes
Given a proposed change When a user creates an A/B test with split=50/50 and unit=accountId Then traffic is deterministically bucketed and exposures are logged with testId and variant Given an active test When metrics are computed daily Then the system reports key metrics and a significance flag where p_value < 0.05 for uplift Given a holdout test with holdout=10% When the change is applied to the remaining 90% Then the holdout segment continues to use the prior version until test end or cancel Given a test stop is requested Then final aggregated results are generated and stored, and full rollout is blocked unless the test is marked "Pass" by configured guardrails Given duplicate exposure events for the same unit and testId When ingested Then they are de-duplicated to a single exposure record
Oscillation Safeguards and Anomaly Alerts
Given rate_limit_per_segment=1 per 24h When a second apply targeting the same segment is attempted within 24h Then the apply is blocked with 429 "rate_limited" and logged Given oscillation_threshold=3 alternating direction changes > 10% within 7 days When the pattern is detected Then the change is blocked pending review and an alert is generated Given an oscillation alert When generated Then notifications are sent to configured channels (email/Slack/webhook) within 5 minutes and include links to the related versions Given an admin override flag with rationale When provided by a user with role "Admin" Then the rate-limit or oscillation block can be bypassed and the override is recorded in the audit trail
Audit Exports and Webhooks for Compliance
Given a request to export audit history with format=CSV and date range Then the system generates a downloadable export including who, when, action, diff, rationale, hintIds, approvals, and versionId Given an export with redaction_enabled=true Then configured PII fields are masked or removed in the output Given a webhook subscription with secret is configured Then each event (proposed, approved, applied, rollback, ab_test_started, ab_test_completed, oscillation_alert) is delivered with HMAC-SHA256 signature and unique eventId Given webhook delivery fails with non-2xx Then retries occur with exponential backoff for up to 24 hours and deliveries are idempotent by eventId Given normal operation Then 95th percentile webhook delivery latency is <= 2 seconds over a rolling 1-hour window and is observable via metrics

Stakeholder Views

Role-based overlays that present the ranked queue through each stakeholder’s lens—PM focus on activation, CX on volume x ARR, Exec on renewal risk. Share a link to a view to align fast while keeping one source of truth under the hood.

Requirements

Weighting & Scoring Engine per Role
"As a product admin, I want to define role-specific scoring formulas so that each stakeholder sees rankings aligned to their KPIs and can make faster, data-driven decisions."
Description

A configurable scoring engine that applies role-specific weighting across metrics to rank feedback themes for PM, CX, and Exec lenses. Supports formulas such as activation impact and recency for PMs, volume × ARR for CX, and renewal/churn risk for Execs, with confidence weighting, recency decay, caps, and deterministic tie-breaking. Provides score explainability by exposing metric contributions per theme. Configuration is managed via versioned JSON and an admin UI, with environment-based defaults and feature-flagged rollout. The engine feeds the ranked queue, live theme map, exports, and shareable views to preserve a single source of truth. Includes unit/integration tests and performance targets to handle live re-scoring without noticeable latency.

Acceptance Criteria
PM Lens Scoring Formula Applied
Given Production defaults for PM: weights.activation_impact=0.70 and weights.recency_score=0.30, recency_score=round(100*0.5^(age_days/14),2), confidence_multiplier=clamp(confidence,0.5,1.0) And final_score=round(min(100,(0.70*activation_impact + 0.30*recency_score) * confidence_multiplier),2) And theme T1 has activation_impact=80, age_days=14, confidence=0.80 When the engine scores role=PM for T1 Then T1.final_score = 56.80 And the ranked queue for role=PM is sorted strictly by final_score descending with deterministic tie-breaking when equal
CX Lens Volume × ARR Scoring Formula Applied
Given Production defaults for CX: volume_n = round(100*log10(1+volume_30d)/log10(1+1000),2) and arr_n = round(100*min(ARR_affected,1000000)/1000000,2) And base = round((volume_n * arr_n)/100,2) and confidence_multiplier=clamp(confidence,0.5,1.0) And final_score = round(min(100, base * confidence_multiplier),2) And theme C1 has volume_30d=100, ARR_affected=$250000, confidence=0.90 When the engine scores role=CX for C1 Then C1.volume_n = 66.80 and C1.arr_n = 25.00 and C1.base = 16.70 And C1.final_score = 15.03 And the ranked queue for role=CX is sorted strictly by final_score descending with deterministic tie-breaking when equal
Exec Lens Renewal/Churn Risk Scoring Formula Applied
Given Production defaults for Exec: arr_at_risk_n = round(100*min(ARR_at_risk_next_90d,2000000)/2000000,2) and risk_prob_n = round(100*clamp(risk_probability,0,1),2) And window_factor = 1.0 if days_to_renewal in [0,30], 0.75 if in [31,60], else 0.50 if in [61,90] And confidence_multiplier=clamp(confidence,0.5,1.0) And final_score = round(min(100, (arr_at_risk_n * risk_prob_n / 100) * window_factor * confidence_multiplier),2) And theme E1 has ARR_at_risk_next_90d=$600000, risk_probability=0.60, days_to_renewal=21, confidence=0.90 When the engine scores role=Exec for E1 Then E1.arr_at_risk_n = 30.00 and E1.risk_prob_n = 60.00 and base = 18.00 and window_factor=1.00 And E1.final_score = 16.20 And the ranked queue for role=Exec is sorted strictly by final_score descending with deterministic tie-breaking when equal
Confidence Weighting, Recency Decay, and Caps Behavior
Given any role, confidence_multiplier is clamp(confidence,0.5,1.0) and if confidence is null or missing then confidence_multiplier=0.75 And all normalized metrics are constrained to [0,100]; negative inputs are treated as 0 before normalization And final scores are capped at 100 and floored at 0 and rounded to 2 decimal places And for PM, recency_score = round(100*0.5^(age_days/14),2) When a PM theme has age_days=28 Then recency_score = 25.00 When any theme has confidence=0.30 Then confidence_multiplier = 0.50 When any theme has confidence=1.20 Then confidence_multiplier = 1.00 When any theme’s computed score exceeds 100 before capping Then the returned final_score = 100.00
Deterministic Tie-Breaking Across Themes
Given two or more themes produce equal final_score after rounding to 2 decimals When ordering the ranked queue for any role Then ties are broken in this stable order: (1) higher ARR_affected first, (2) newer last_seen_at first, (3) lower theme_uid (lexicographic) first And given T2 and T3 both have final_score=56.80, with T2.ARR_affected=$300000 and T3.ARR_affected=$150000 Then T2 appears before T3 And given T4 and T5 both have final_score=42.00 and equal ARR_affected, with T4.last_seen_at more recent than T5 Then T4 appears before T5 And given T6 and T7 both have final_score=37.50, equal ARR_affected and equal last_seen_at, with T6.theme_uid < T7.theme_uid lexicographically Then T6 appears before T7
Score Explainability Output Per Theme
Given a scored theme for any role When requesting the theme’s explain API for that role Then the response contains: role, final_score, cap_applied (boolean), rounding=2dp, config_version, computed_at (ISO8601), contributions[] with entries {metric_key, raw_value, normalized_value, weight, contribution}, multipliers {confidence_multiplier, window_or_recency_factor}, and tie_breakers_evaluated (ordered list) And the sum of contributions equals the pre-multiplier base within ±0.01 And base multiplied by all multipliers equals the final_score within ±0.01 And the explain API responds within p95 ≤ 200ms for 1,000 themes requested in batch mode
Config Versioning, Feature Flags, Consistency Across Views, and Performance
Given an admin edits scoring JSON via the UI and passes schema validation for version=semver (e.g., 1.2.0) When the config is saved behind feature flag role_scoring_engine_v1 for environment=Production Then a new immutable config_version is created with author, timestamp, environment, and diff summary stored And rollback to the previous config_version can be executed in one action And within p95 ≤ 250ms the engine re-scores 1,000 themes, and within 1s the ranked queue and live theme map reflect the new scores And exports (CSV/JSON) and shareable stakeholder view links created after save reflect the same config_version and scores as the ranked queue And unit tests cover ≥90% lines and ≥85% branches for the scoring module; integration tests verify stable ordering (including tie-breakers) across runs on a golden dataset
Role-Based View Templates
"As a stakeholder, I want a ready-made view tuned to my role so that I can get to the most relevant insights immediately without manual configuration."
Description

Predefined overlays for PM, CX, and Exec that render the ranked queue with role-appropriate columns, KPIs, and visual cues. Each template binds to the scoring engine, applies default filters (e.g., product area, timeframe), and surfaces lens-specific metrics like activation delta (PM), volume × ARR (CX), and renewal risk trend (Exec). Templates include quick toggles (confidence threshold, recency window), contextual tooltips, and consistent color semantics across the app. They preserve the single source of truth by displaying live data rather than copies and can be set as a user’s default landing view.

Acceptance Criteria
Role Templates Render Correct Lens-Specific Overlays
Given a PM-role user selects the PM template When the ranked queue loads Then the columns equal [Theme, Activation Delta, Confidence, Impact Score, Product Area, Recency] And the header KPIs show Activation Delta (avg) and Week-over-Week Delta And no columns for Volume × ARR or Renewal Risk Trend are visible. Given a CX-role user selects the CX template When the ranked queue loads Then the columns equal [Theme, Volume, ARR, Volume × ARR, Confidence, Product Area, Recency] And the header KPIs show Total Volume and ARR Coverage And Activation Delta and Renewal Risk Trend are not present. Given an Exec-role user selects the Exec template When the ranked queue loads Then the columns equal [Theme, Renewal Risk Trend, ARR at Risk, Confidence, Product Area, Recency] And the header KPIs show At-Risk ARR (30d, 90d) and Trend Direction And Activation Delta and Volume × ARR are not present.
Templates Bind to Live Scoring and Update Timely
Given a theme’s score changes in the scoring engine When the template view is open Then the row’s metrics and rank update within 60 seconds without manual refresh And the Last updated timestamp reflects the refresh time within ±5 seconds And clicking the theme opens the canonical theme detail with identical ID and metrics (no data copies).
Default Filters Apply and Reset to Template Defaults
Given a new user opens each template for the first time When the view loads Then defaults apply as follows: PM=Timeframe Last 14 days, Product Area=All; CX=Last 30 days, All; Exec=Last 90 days, All. Given a user clicks Reset Filters When the action is confirmed Then the filters revert to the template defaults above.
Quick Toggles for Confidence Threshold and Recency Window Persist
Given any template view is open When the Confidence Threshold is set via slider 0.00–1.00 (step 0.05) Then only rows with Confidence ≥ threshold remain and ranking recomputes within 1 second. Given the Recency Window is set to 7/14/30/60/90 days When applied Then results filter to items within the chosen window and KPIs update within 1 second. Given a user revisits the same template When the view loads Then the last-chosen Confidence Threshold and Recency Window persist for that user and template until Reset.
Contextual Tooltips and Color Semantics Are Consistent and Accessible
Given a user hovers or focuses on Activation Delta, Volume × ARR, or Renewal Risk Trend When a tooltip is requested Then a tooltip appears within 500 ms with definition, formula, and time basis And the tooltip is keyboard-accessible (Tab/Shift+Tab) and dismissible with Esc. Given any positive/negative/neutral visual cue is displayed When inspected Then color semantics match the app standard (e.g., positive=green, negative=red, neutral=gray) And all cues pass WCAG AA contrast And a non-color indicator (icon or text) accompanies the color cue.
Set Template as User Default Landing View
Given a user clicks Set as default on a template When the action is confirmed Then subsequent logins land on that template with the user’s saved toggles and filters And changing the default updates immediately And users can revert to system default via Settings → Views → Default Template And the default synchronizes across devices for the same account.
Customizable Filters & Saved Views
"As a PM, I want to customize filters and save my configuration as a reusable view so that I can return to it and share a consistent perspective with my team."
Description

Enable users to refine a stakeholder view with filters and sorts (product area, customer segment, ARR band, plan tier, lifecycle stage, channel, confidence, timeframe) and save the configuration as personal or team-shared presets. Saved views store only configuration metadata and always fetch live data to maintain one source of truth. Provide naming, descriptions, default-per-role options, and a quick switcher. Include guardrails to prevent conflicting filters and ensure consistent sorting with the role’s scoring formula.

Acceptance Criteria
Save Personal Preset with Multi-Filters and Sort
- Given PM overlay is active in Stakeholder Views - When the user applies filters: product area=Onboarding; customer segment=SMB; ARR band=10k–50k; plan tier=Pro; lifecycle stage=Active; channel in [Tickets, Chats]; confidence >= 0.70; timeframe=Last 30 days - And sets sort: primary=PM Activation Score (desc), secondary=Volume (desc) - And saves as Personal with name "PM-Activation-SMB-L30" and description "SMB onboarding issues last 30 days" - Then a personal saved view is created and appears in the quick switcher under Personal - And the name is 3–60 characters, unique within the user's personal views, and limited to letters, numbers, spaces, hyphens, and underscores; invalid entries show inline error and disable Save - And description is optional and <= 280 characters - And re-opening the saved view reapplies the exact filter and sort configuration - And opening the saved view dispatches a data fetch within 200 ms and renders live results; newly ingested matching items appear without modifying the preset
Create Team-Shared Preset with Role Default
- Given Exec overlay is active - When a user with Editor or Admin permission saves a view as Team Shared named "Exec–Renewal Risk L90" and checks "Set as default for Exec" - Then the saved view visibility is Team and it appears in the quick switcher under Team - And all workspace members with Viewer+ can open the view; only Editor/Admin can edit or delete it - And for users without a personal default, opening the Exec overlay auto-loads this team default - And a user's personal default for Exec, if set, takes precedence over the team default - And updates to the shared preset propagate to all users on next load without requiring client reconfiguration - And the shareable view URL opens the same configuration for authorized users and respects visibility permissions
Guardrails: Prevent Conflicting Filters
- Given any overlay is active - When the user sets confidence min > max (e.g., min=0.8, max=0.5) - Then both fields are highlighted, an inline validation message appears, and Save is disabled until corrected - When the user attempts to select multiple timeframes (e.g., Last 7 days AND Custom Range) - Then only one timeframe can be active; selecting a second deselects the first - When filters create a logical contradiction (e.g., lifecycle stage=Prospect AND channel in [Tickets, Emails] where Prospect is defined as pre-customer) - Then a warning explains the contradiction, offers to remove one filter, and Save remains disabled until resolved - When a single-select field (e.g., plan tier) is given multiple values - Then the UI enforces single selection and prevents multiple values
Sorting Consistency with Role Scoring Formula
- Given the CX overlay uses "Volume × ARR" as the primary ranking - When the user applies a custom sort (e.g., Confidence desc) - Then primary ordering remains by "Volume × ARR" and the custom sort applies only as a secondary tiebreaker - And the relative order of the top 3 items by the overlay's primary formula is unchanged before vs. after applying the custom sort - When the user attempts to set a different field as the primary sort - Then the UI prevents overriding the primary formula and displays a tooltip indicating the primary ranking is fixed by the role's scoring - And API/export for the saved view includes sort keys: primary=role formula, secondary=user-chosen field
Quick Switcher: Discoverability and Performance
- Given any page within Stakeholder Views - When the user presses Cmd+K (Mac) or Ctrl+K (Win/Linux) or clicks the quick switcher icon - Then the quick switcher opens within 150 ms and shows Recent, Personal, Team, and Role Default sections - And typing filters results using fuzzy match across name and description, updating within 100 ms per keystroke - And arrow keys navigate results; Enter applies the highlighted view; Esc closes and returns focus to the triggering control - And selecting a view applies its configuration and triggers data fetch; visible state updates within 300 ms with a loading indicator until data arrives
Saved View Stores Only Configuration Metadata
- Given a saved view exists - When an authorized user retrieves it via API - Then the response includes only configuration metadata (id, name, description, visibility, role, filters, sort, owner, createdAt, updatedAt) and no records, counts, or cached data snapshot - When underlying data changes to include new items matching the filters - Then opening or refreshing the saved view displays the new items without changing the saved view object - And the UI shows a Live Data indicator with a last refreshed timestamp reflecting the most recent fetch, not the save time
Shareable Links with Access Control
"As an executive, I want to share a secure link to my view so that stakeholders can align quickly on the latest priorities without exporting or duplicating data."
Description

Generate secure, shareable links to any stakeholder view or saved preset with permissions (org-wide, team, specific users), expiration dates, and view-only vs. can-edit scopes. Links encode the configuration and retrieve live data on open, ensuring recipients see the current source of truth. Support SSO enforcement, optional passcodes, link revocation, and analytics on opens. Deep-link to specific themes or map selections and preserve scroll/sort state for fast alignment in reviews and standups.

Acceptance Criteria
Create Shareable Link with Audience and Scope
Given a logged-in user with Share permission opens a stakeholder view or saved preset When they click "Create Shareable Link" and select one audience option (Org-wide, Team, Specific Users) and one scope (View-only, Can-edit), then click "Generate" Then a unique URL is created that encodes the selected view/preset, filters, sort, and selected audience/scope, and the URL is copyable to clipboard And Org-wide audience allows any authenticated org member to access; Team restricts access to members of the specified team(s); Specific Users restricts access to the selected users only And View-only disables changing filters/sort and saving edits; Can-edit allows changing filters/sort and saving back to the preset if the recipient has edit rights And an unauthorized recipient (outside the selected audience) is denied with HTTP 403 and an in-app "You don’t have access" message
Enforce Expiration Date on Link Access
Given the sharer sets an expiration date/time (UTC) while generating or editing a link When a recipient opens the link before the expiration timestamp Then access is granted When a recipient opens the link at or after the expiration timestamp Then access is denied with HTTP 410 Expired and an in-app message; the link cannot be used to fetch data And updating the expiration time takes effect within 60 seconds for all subsequent opens And the link management UI displays the scheduled expiration time
Require SSO and Optional Passcode on Open
Given the org has "Enforce SSO on shared links" enabled When a recipient opens the link Then they must authenticate via the configured SSO provider before any view data loads And if the recipient lacks an org account or fails SSO, access is denied with HTTP 401 and guidance to request access And if an optional passcode is set on the link, the recipient is prompted for the passcode after SSO; the correct passcode grants access; an incorrect passcode blocks access and displays a non-revealing error And if SSO enforcement is off and a passcode is set, the passcode gate is required before content loads
Revoke Link and Invalidate Access Immediately
Given a valid shareable link exists When the owner or an admin clicks "Revoke" in link management and confirms Then the link token is invalidated immediately (≤15 seconds) across all regions And any new open attempts return HTTP 410 Revoked with an in-app message And any active sessions using the link are blocked on the next API request with HTTP 401 and prompted to refresh
Capture and Surface Link Open Analytics
Given a shareable link is created When recipients open the link Then the system records an open event with timestamp, link id, and user id when identifiable (authenticated); anonymous opens are recorded as such And link analytics display in the link management UI: total opens, unique viewers, last opened at, and a viewer list (name/email) when identifiable And analytics counters update within 60 seconds of an open And deleting a link removes its analytics from the UI
Deep-Link to Themes/Map and Preserve UI State
Given the sharer has a view with selected theme(s) or a map selection, a specific sort order, and a current scroll position When they generate a shareable link Then opening the link navigates directly to the target theme or map selection, applies the encoded sort order, and restores the scroll position within the list/map And if the target theme no longer exists, the link opens the parent cluster and displays a notice that the target is unavailable And state restoration behaves consistently across Chrome, Edge, Safari, and Firefox (latest two major versions)
Retrieve Live Data on Link Open (Source of Truth)
Given a link was generated at time T0 When underlying data (themes, confidence scores, ranks) changes at time T1 > T0 And a recipient opens the link at time T2 ≥ T1 Then the recipient sees data reflecting the state at T2 (including the changes at T1), not a snapshot from T0 And the URL contains configuration only; all data is fetched from the server on open And if data changes again while the page is open, a manual refresh or navigation re-fetch displays the current state
Real-Time Sync with Theme Map and Queue
"As a CX lead, I want my stakeholder view to reflect changes in real time so that I always act on the most current themes and priorities."
Description

Keep stakeholder views in lockstep with the live theme map and ranked queue via push updates. When clusters merge/split or scores change, the open view updates in place with low-latency, shows subtle change indicators, and preserves user scroll and selections. Include offline detection, retry logic, and conflict handling to avoid jarring resorting. Provide lightweight performance budgets and monitoring to ensure smooth updates at scale.

Acceptance Criteria
Low-Latency Push Update on Score Change
Given a stakeholder view with ranked queue and theme map is open And the client is online and subscribed to live updates When the backend publishes a score change affecting one or more items Then the affected queue rows and theme map nodes reflect new scores within 1.5s p95 and 3s p99 of the server event timestamp And updates are applied via incremental diff without full-page reload or flicker And a subtle change indicator (e.g., arrow with delta) appears next to each changed item for 5–10s and then fades without shifting layout
Cluster Merge and Split Rendering with Indicators
Given a stakeholder view is open with the current ranked queue and theme map When two clusters merge on the backend Then exactly one consolidated item appears in the queue and a single node in the theme map with combined metrics within 2s p95 And a "merged" indicator badge is shown on the affected item/node for 10s and then fades And no duplicate or orphaned items remain When a cluster splits on the backend Then both new clusters appear with correct metrics and distinct identifiers within 2s p95 And a "split" indicator badge is shown on the new items/nodes for 10s and then fades And transitions are animated (200–400ms) without freezing the UI
Viewport, Scroll, and Selection Preservation
Given a user has scrolled the queue and has one or more items selected and a details panel open When live updates arrive that change scores or reorder items Then the viewport scroll position changes by no more than 16px and does not jump to top And the selected item(s) remain selected and focused And if a selected item is merged, the selection transfers to the merged item; if split, selection transfers to the logically corresponding child (by highest contribution) and the details panel remains open And no keyboard focus is lost and tab order is preserved
Offline Detection and Seamless Retry
Given the client is viewing a stakeholder view When the network connection drops or the live-updates channel errors Then an offline/stale banner appears within 2s and the client stops applying partial updates And the client retries with exponential backoff and jitter up to 30s between attempts And upon reconnection within 5 minutes, all missed updates are fetched and applied in order without duplicates And if offline exceeds 5 minutes, a manual "Refresh" action is shown and succeeds in restoring the latest state
Conflict Handling and Stable Sorting
Given multiple rapid updates would cause repeated reordering of the ranked queue When updates are received within a 1s window Then updates are coalesced and applied as a single animated reorder (200–400ms) And a stable secondary sort key (e.g., clusterId) is used for ties to prevent oscillation And pinned or hovered items do not move until user idle for at least 1s And no more than one reorder batch is applied per second while the user is interacting
Performance Budgets and Monitoring at Scale
Given a dataset of 2k clusters and a visible queue of 50 rows When an update batch affecting up to 20 items is applied Then diff computation and DOM patching complete within 100ms p95 on a mid‑range laptop And main thread blocking time remains under 50ms p95 during the update And frame drops remain below 5% over a 10s update burst test And live-update client code adds no more than 25KB gzipped to the bundle And monitoring emits update_received, diff_applied, reorder_applied with durations and a dashboard shows p95/p99 within 24h And an alert triggers if p95 end-to-end update latency exceeds 2s for 5 consecutive minutes
Shared View Link Consistency Across Stakeholders
Given two stakeholders open the same shared view link with identical filters When a merge, split, or score change occurs Then both clients display the same ordering, badges, and indicators with arrival time skew ≤ 1s p95 And personal UI preferences (density, column visibility) do not affect the shared view state for other users And the view URL encodes filters and role lens so a reload reproduces the same state
Inline Issue Creation from Views
"As a product manager, I want to create Jira/Linear tickets directly from my view so that I can push high-impact fixes without context switching."
Description

Allow creation and update of Jira/Linear issues directly from a stakeholder view with one click. Prefill role-relevant context (top evidence, affected accounts, ARR impact, confidence, suggested severity), support batching of related themes, and ensure idempotent sync to avoid duplicates. Show inline sync status, errors, and links back to issues. Respect existing connector permissions and map custom fields via per-workspace configuration.

Acceptance Criteria
Role-Specific Context Prefill Across Stakeholder Views
Given a connected Jira or Linear integration and a user in a Stakeholder View (PM, CX, or Exec) When the user clicks Create Issue on a ranked theme Then the created issue is prefilled with top 3 evidence excerpts with source links, affected accounts count and up to 10 sample account names, total ARR impact with currency, confidence score as a percentage, and suggested severity mapped to the destination priority field And the issue summary is "<Theme Title> — <Role Lens>" And the description includes a backlink to the EchoLens theme and the originating view URL And in PM view the prefill includes activation/journey stage context And in CX view the prefill includes volume × ARR and the top source channel And in Exec view the prefill includes renewal risk flag and ARR at risk And the default destination issue type configured for the workspace and role is used
One-Click Issue Creation to Correct Destination
Given connectors are configured with a default destination per role (Jira project/issue type or Linear team/issue type) When the user one-clicks Create Issue from a theme card Then the issue is created in the configured default destination without additional prompts And the operation returns a link containing the external issue key/ID And the sync status transitions to Synced within 5 seconds for 95% of attempts And if no default destination exists, a project/team selector is shown inline and the user's selection is persisted for subsequent actions
Inline Update of Linked Issues
Given a theme already linked to a Jira or Linear issue When the user edits mapped fields inline (priority/severity, assignee, labels/tags, custom fields, description) and clicks Sync Then the corresponding fields are updated in the external issue And the sync status shows Synced within 5 seconds for 95% of attempts And if a version conflict is detected because the external issue changed since last fetch, a conflict message is shown and the user can Refresh before retrying
Idempotent Sync and Duplicate Prevention
Given a theme has an existing linkage to a destination (workspaceId + destinationType + project/teamId + themeId + issueType [+ batchGroupId]) When Create Issue is triggered again for the same combination Then no new issue is created and the existing issue link is surfaced And retries after a transport error do not create duplicates And if an issue already exists remotely with an EchoLens backlink or a "Theme:<id>" tag but no local link, the system re-associates it instead of creating a new one
Batch Operations for Related Themes
Given a user multi-selects 2–50 related themes in a view When they choose Batch Create Then the user can select One per theme or Consolidated mode And in One per theme mode, N issues are created and, if configured, linked to a single parent Epic/parent issue created once And in Consolidated mode, one issue is created with a section per theme and a deduplicated list of affected accounts and evidence And batch status reports per item (success/failure) and offers Retry Failed for only failed items And all created issues include backlinks to each source theme and the originating batch view
Inline Sync Status, Errors, Retry, and Links
Given a create or update action is initiated When the sync runs Then a status chip displays one of: Pending, In Progress, Synced, Failed And on success, the chip includes the external issue key/ID as a clickable link opening in a new tab And on failure, the chip shows the error code and a truncated message (<=200 chars) with a View details toggle for full payload/log And a Retry action is available and executes idempotently And the status auto-refreshes at least every 2 seconds until a terminal state is reached
Permissions and Custom Field Mapping Enforcement
Given a user's connector token scopes and project/team permissions When the user attempts to create or update Then the action is allowed only if the token has create/update rights on the target; otherwise the button is disabled with an Insufficient permission tooltip And if permission errors occur during the API call, the action fails with a clear message and remains unsynced And configured per-workspace custom field mappings are applied (Jira customfield_xxxx IDs, Linear custom field slugs) with correct data types And if a mapped required field lacks a value, the operation is blocked with an explicit list of missing fields And default values defined in workspace mapping are populated when present
Governance & Audit Trail for Views
"As an admin, I want a full audit trail of stakeholder views and shares so that I can ensure governance, troubleshoot changes, and meet compliance requirements."
Description

Track creation, edits, sharing, and access for stakeholder views and templates, including actor, timestamp, changes to weights/filters, and recipients. Provide admin reporting, CSV export, and retention controls aligned to workspace policies. Surface recent activity within the view to increase trust and facilitate decision traceability during reviews and audits.

Acceptance Criteria
Creation & Edit Audit for Views and Templates
Given a user with edit permission creates a stakeholder view or template, When they save it, Then an audit event is recorded with actor, role, timestamp (ISO 8601), object_type (view|template), object_id, action=create, and initial weights/filters snapshot. Given a user edits an existing view/template, When they save changes, Then an audit event is recorded with action=update, fields_changed list, and before/after values for weights/filters. Given multiple edits occur in quick succession, When each save happens, Then each is logged as a distinct event with sequential ordering and non-duplicated timestamps. Given a user without permission attempts to edit, When the system blocks the change, Then a failed_action audit event is recorded with reason=authorization_denied and no configuration change applied. Given the audit timeline for an object is requested, When events are fetched, Then the chronological history reconstructs the configuration state without gaps or overwrites.
Share & Access Events for Stakeholder Views
Given a user generates a share link for a view, When link creation succeeds, Then an audit event is recorded with action=share_link_created, scope, expiration (if any), recipients (emails/groups where applicable), actor, and timestamp. Given a user updates share settings or revokes a link, When saved, Then an audit event is recorded with action in {share_updated, share_revoked} including previous vs new scope/recipients. Given a recipient opens a shared view, When access is granted, Then an access event is recorded with actor (resolved identity or anonymous token), timestamp, access_channel (link|internal), and view_version. Given an access attempt is denied due to revoked/expired link, When the attempt occurs, Then an access_denied event is recorded with reason in {expired, revoked, scope_mismatch}. Given an email invite is sent for sharing, When the email is dispatched, Then recipients are logged but message content is not stored in the audit log.
Recent Activity Panel Within a View
Given an authorized user opens a view, When they expand Recent Activity, Then the latest 20 events for that view are displayed in reverse chronological order with actor display name, action summary, and relative time. Given an event reflects a configuration change, When shown in Recent Activity, Then the key fields changed (weights/filters) are summarized with before→after values. Given the view has no prior events, When Recent Activity is opened, Then an empty state displays with text indicating no recent activity. Given a non-admin views Recent Activity, When actor details are shown, Then emails are redacted and only name and role are displayed. Given the user clicks View full audit from the panel, When clicked, Then they are navigated to a full audit timeline scoped to the view with filters pre-applied.
Admin Audit Reporting & CSV Export
Given an admin opens Audit Reports, When they filter by date range, actor, action type, object type, or object ID, Then the results reflect the filters and return within 2 seconds for up to 10,000 matching events. Given an admin clicks Export CSV on filtered results ≤50,000 rows, When the export completes, Then the CSV includes header and columns: event_id, timestamp_iso, actor_id, actor_role, action, object_type, object_id, view_template_version, fields_changed, before_values, after_values, recipients, access_channel, outcome, reason, workspace_id. Given filtered results exceed 50,000 rows, When Export CSV is requested, Then a background job is queued, the admin is notified in-app, an email with a secure download link is sent within 5 minutes, and an export_requested audit event is recorded. Given no results match the filters, When viewing the page, Then Export CSV is disabled and a No results message is shown. Given an admin downloads a generated CSV, When the download starts, Then a csv_downloaded audit event is recorded with export job ID and file checksum.
Retention Policies & Legal Hold Compliance
Given a workspace admin sets an audit retention period between 30 and 1825 days, When saved, Then a policy_changed audit event is recorded including previous vs new values and effective timestamp. Given events exceed the configured retention window, When the nightly purge job runs, Then eligible events are irreversibly deleted and a purge_summary event records the count purged and date range affected. Given a legal hold is placed on a specific view, template, or actor, When purge runs, Then events under hold are excluded from deletion until the hold is removed, and each hold change is audit-logged. Given retention is reduced, When the next purge runs, Then the system purges older data according to the new window and does not purge events currently under legal hold. Given retention is increased, When the next purge runs, Then no events newer than the new window are purged.
Permissions, Integrity, and Tamper Protection for Audit Logs
Given audit logs exist, When a non-admin user attempts to access the global Audit Reports, Then access is denied with a 403 and no event data is returned. Given audit logs exist, When an admin or auditor role requests logs, Then results include full details subject to workspace policy and are paginated and sortable. Given an audit event is written, When persisted, Then it is immutable; corrections generate a new event referencing the prior event_id and no in-place updates occur. Given a CSV export is generated, When the file is created, Then a SHA-256 checksum is produced and displayed with the download and stored with the export record. Given audit logs are accessed via API, When valid admin credentials are used, Then filtering (date, actor, action, object) and pagination (limit/offset or cursor) behave consistently with the UI and rate limits are enforced.

Source Radar

Automatically discovers your organization’s Zendesk, Intercom, Jira/Linear, and app‑store accounts via domain and SSO signals, then recommends the right connections. Eliminates hunting for IDs and reduces setup to minutes so you can ingest feedback immediately.

Requirements

Domain & SSO-Based Source Discovery
"As a product manager setting up EchoLens, I want the system to auto-detect our support and dev tool accounts from our domain and SSO so that I can connect the right sources without hunting for IDs."
Description

Leverages the organization’s verified domain and active SSO context to automatically detect Zendesk, Intercom, Jira, Linear, and app-store accounts. Uses DNS records (MX, TXT), HTTP headers, provider well-known endpoints, and public directories to surface candidate tenants and identifiers. Normalizes and deduplicates findings into a structured list containing provider, tenant ID/subdomain, evidence types, and a preliminary match score. Applies provider-specific heuristics and strict rate limiting; never brute-forces. Exposes results via the onboarding flow and an internal API to seed recommendations, minimizing manual lookup and accelerating initial ingestion setup within EchoLens.

Acceptance Criteria
Auto‑discover Providers via Verified Domain and Active SSO
Given an organization with a verified primary domain and an active SSO context When Source Radar initiates discovery Then it queries only DNS MX/TXT records, provider well‑known endpoints, HTTP headers, public directories, and SSO app metadata for the supported providers (Zendesk, Intercom, Jira, Linear, Apple App Store, Google Play) And for each provider, any detected tenant/subdomain is captured as a candidate with provider, tenant_id/subdomain, evidence_types, and preliminary_match_score populated And every candidate includes at least one evidence_type and a preliminary_match_score between 0 and 100 And candidates corroborated by both domain signals and SSO context have a score >= 70 And candidates with a single evidence source have a score between 30 and 69 And providers not in the supported list are not queried or returned as candidates
Normalization and Deduplication of Candidates
Given multiple discovery signals map to the same tenant (e.g., acme.zendesk.com) When results are normalized Then a single candidate is produced with a merged set of unique evidence_types and one tenant_id/subdomain And redirecting or aliased subdomains are collapsed into that candidate And distinct tenants (e.g., acme.zendesk.com vs acme-support.zendesk.com) remain separate entries And the final list is sorted by preliminary_match_score in descending order and tie-broken deterministically by provider then tenant And all candidates are free of exact duplicates
Provider‑Specific Heuristics Application
Given provider-specific heuristics for tenant identification (e.g., *.zendesk.com, *.atlassian.net for Jira, Linear team discovery via well‑known endpoints, Intercom app/workspace identifiers, and app store developer listings) When candidates are evaluated Then false positives such as generic landing pages or unclaimed tenants are filtered out And Jira candidates are included only if the Atlassian site claim or SSO app listing matches the verified domain And Intercom candidates are included only if the app/workspace is associated with the verified domain via SSO or verified email domain And App Store candidates are included only if the developer website or support URL contains or matches the verified domain And each applied heuristic is recorded in evidence_types or match_reason for traceability
Strict Rate Limiting and Non‑Brute‑Force Discovery
Given default per‑provider limits of max 1 request per second and 60 requests per hour When discovery runs Then outbound requests never exceed the configured limits per provider And if a limit would be exceeded, discovery for that provider pauses and schedules a retry no sooner than 15 minutes later And no dictionary or subdomain enumeration is performed; requests are made only to known provider endpoints derived from evidence or well‑known patterns And an audit log records counts per provider and evidence type for each run without storing PII or credentials
Onboarding Flow Surfaces Recommendations
Given a newly verified domain reaches the "Connect your sources" onboarding step When the step loads Then the candidate list renders within 5 seconds with provider, tenant_id/subdomain, evidence badges, and preliminary_match_score visible for each candidate And by default only candidates with preliminary_match_score >= 40 are shown, with a control to reveal lower‑confidence candidates And each candidate offers Connect (pre‑filling tenant/subdomain into the connector) and Dismiss actions that persist immediately And if no candidates exist, a clear "No sources found" state is shown with a link to manual connection instructions
Internal API Exposes Structured Discovery Results
Given a service-to-service authorized client When it calls GET /internal/v1/source-discovery?org_id={id} Then the response is 200 with a JSON array of candidates each containing provider (enum: zendesk|intercom|jira|linear|app_store_apple|app_store_google), tenant (tenant_id or subdomain), evidence_types (array of enums: dns_mx|dns_txt|http_header|well_known|sso_app|public_directory), preliminary_match_score (0-100 number), discovered_at (ISO 8601), and match_reason (string) And results are sorted by preliminary_match_score descending And an org with no findings receives 200 with an empty array And unauthorized or unauthenticated callers receive 401 or 403 and no data
SSO Context Influence and Cross‑Verification
Given the user is authenticated via SSO (e.g., Okta, Azure AD, Google) and the IdP lists installed apps for Zendesk, Intercom, Jira, or Linear When discovery runs Then SSO app metadata contributes an evidence_type of sso_app and increases preliminary_match_score by at least 20 points when matched to the verified domain And if SSO app metadata does not align with the verified domain, the candidate is excluded or capped at a score <= 39 And discovery uses only read‑level SSO metadata and does not request or store admin‑level credentials
SSO App Inventory Ingestion
"As an IT admin, I want Source Radar to leverage our SSO app catalog to suggest integrations so that setup is accurate, quick, and secure."
Description

Ingests sanctioned application inventory from the identity provider (e.g., Okta, Azure AD, Google Workspace) using OIDC discovery and optional admin-granted read scopes to enrich discovery with authoritative app-to-tenant mappings. Performs minimal, read-only access; does not persist credentials or excessive PII. Correlates IdP app entries with supported providers to improve precision and reduce false positives. Provides fallback behavior when IdP data is unavailable, maintaining a privacy-first posture and clear user controls.

Acceptance Criteria
OIDC Discovery and Least-Privilege Consent
Given an IdP admin initiates SSO with EchoLens via Okta, Azure AD, or Google Workspace When EchoLens requests OAuth/OIDC consent for sanctioned app inventory Then only read-only scopes required to list applications are requested and displayed And the consent screen accurately names each scope and purpose And access is blocked if consent is denied or partial And OIDC discovery document is fetched via HTTPS and issuer, jwks, and audience are validated And no OAuth access tokens, refresh tokens, or client secrets are persisted beyond in-memory use And database inspection after ingestion shows zero stored tokens or secrets
Authoritative App Inventory Ingestion
Given an IdP admin has granted read-only app-inventory scopes When ingestion is started Then EchoLens retrieves the current sanctioned app inventory from the IdP And the number of active apps ingested matches the IdP-reported active count within ±1% And for each app EchoLens stores only: display name, vendor, app unique id, tenant/instance identifier, and app status And for supported providers (Zendesk, Intercom, Jira, Linear, Apple App Store, Google Play) instance identifiers are extracted and normalized (e.g., subdomain, cloud site URL/cloud id, org key, store app id) And p95 ingestion time is ≤ 120 seconds for up to 5,000 app entries And metrics are emitted for apps_discovered, apps_mapped, duration_ms, and error_count
Supported-Provider Correlation and Precision
Given an ingested IdP inventory containing multiple potential matches for supported providers When the correlation engine maps IdP apps to Zendesk, Intercom, Jira, Linear, and app-store instances Then only supported providers are proposed as connectable sources And each proposed connection includes a confidence score in [0.0, 1.0] And at the default threshold of 0.80, precision is ≥ 95% and recall is ≥ 85% on a labeled validation set And duplicate proposals referring to the same instance are deduplicated across IdP sources And user dismissal of a proposal suppresses it from future runs for that organization
Privacy-First Data Handling and Minimization
Given app inventory data is ingested When data is stored and logs are written Then no user-level assignments or identities are ingested by default And only organization-level app metadata fields are stored as defined in the data contract And logs and events contain no tokens, secrets, or personal identifiers (automated redaction enabled) And scheduled PII scans on storage and logs report zero high-severity findings And upon organization disconnect, all stored inventory data is purged within 60 seconds and verified by audit log
Fallback Discovery When IdP Unavailable or Access Denied
Given the IdP API is unreachable, returns 4xx/5xx, or required scopes are not granted When Source Radar runs discovery Then EchoLens switches to domain and SSO signal–based discovery without using IdP data And the UI displays a "Limited mode" banner explaining reduced precision and how to enable IdP access And proposed connections are labeled Unverified and include reduced confidence scores And retries follow exponential backoff with max 5 attempts over 24 hours without user pop-ups And no IdP-derived data is stored in fallback mode
User Review, Control, and Auditability
Given proposed connections are generated from IdP correlation or fallback discovery When a user opens the review screen Then each proposal shows source, instance identifiers, confidence, and requested scopes (if any) And the user can Approve, Skip, or Edit instance details before connecting And the user can revoke a previously approved connection, which removes associated stored metadata within 60 seconds And all actions (approve, skip, edit, revoke) are recorded with actor, timestamp, before/after values, and reason And the audit log can be exported as CSV within 60 seconds for up to 10,000 records
Multi-IdP Merge and Deduplication
Given an organization connects more than one IdP (e.g., Okta and Azure AD) When app inventories are ingested from each IdP Then EchoLens merges inventories and deduplicates entries by canonical provider instance keys (e.g., Zendesk subdomain, Jira cloud id) And conflicting metadata is resolved deterministically by source priority (default: Okta > Azure AD > Google Workspace) or an admin-defined priority And the UI shows per-entry source badges and source count And disconnecting one IdP removes only data unique to that source while retaining entries verified by remaining sources
Confidence-Scored Recommendations
"As a CX lead, I want clear, ranked connection suggestions with reasons so that I can confidently choose the correct accounts quickly."
Description

Combines multi-signal evidence (domain probes, SSO inventory, public metadata, prior user feedback) to generate confidence-scored connection suggestions. Surfaces ranked suggestions with human-readable rationales (e.g., DNS match, verified OIDC app, public subdomain), required scopes, and risk level. Supports accept, ignore, or snooze actions; adapts future recommendations based on user choices. Provides a service API and UI components for the onboarding flow and the admin dashboard.

Acceptance Criteria
Ranked Confidence-Scored Suggestions in Onboarding
Given a new organization with verified domain and SSO configured When Source Radar runs discovery during the onboarding flow Then the system returns 0–50 suggestions ranked strictly by confidence_score descending with a stable tie-breaker on latest_signal_timestamp And each suggestion has a confidence_score in the range 0–100 with two-decimal precision And if no suggestions meet the minimum display threshold (>= 40), an empty state with a retry action is shown instead of a list And the top suggestion is actionable (Accept, Ignore, Snooze) without scrolling on a standard laptop viewport (1366x768) And refreshing the page without new evidence yields identical scores and ordering
Multi-Signal Evidence Aggregation and Rationale Generation
Given candidate accounts discovered via domain probes, SSO inventory, public metadata, and prior user feedback When generating a suggestion Then at least two distinct evidence types must be present OR a single verified SSO app match must be present to surface the suggestion And the rationale includes a minimum of two human-readable entries (e.g., "DNS TXT match", "Verified OIDC app", "Public subdomain match", "Historical connection") referencing the matched values without exposing secrets And each rationale entry is mapped to its evidence_type and confidence contribution percent And recomputing the suggestion with identical inputs produces the same confidence_score within ±0.01 And missing or unreachable evidence sources degrade confidence_score but do not block generation or crash the process
Scopes Disclosure and Risk Classification per Suggestion
Given a surfaced suggestion for a provider When the suggestion is rendered Then required_scopes is non-empty and lists all scopes requested for connection And risk_level is present and in {Low, Medium, High} And any scope containing write/admin/organization-wide permissions elevates risk_level to at least Medium; any admin/global write scope sets risk_level to High And Accept requires explicit user confirmation of scopes; if scopes change between render and accept, the user is warned and must reconfirm And an audit record is written on Accept with fields {suggestion_id, provider, user_id, required_scopes, risk_level, timestamp}
User Actions: Accept, Ignore, and Snooze Behaviors
Given a visible suggestion When the user clicks Accept and authorization succeeds Then a connection is created, the suggestion is removed from lists, an audit event is logged, and subsequent discovery does not resurface the same account And Accept is idempotent using an idempotency key; repeated submissions within 10 minutes return 2xx with the original result When the user selects Ignore (with optional reason) Then the suggestion disappears immediately, is persisted as ignored, and will not reappear for 30 days unless its confidence_score increases by ≥ 15 points from new evidence And the ignore action is reversible via an "Undo" control for 10 seconds and via settings thereafter When the user selects Snooze for 7, 14, or 30 days Then the suggestion is hidden for the selected period and reappears only after the snooze expires; snooze can be canceled from settings
Adaptive Recommendations Based on User Choices
Given a user has ignored suggestions for the same provider family twice within 14 days When computing future suggestions for that organization Then the minimum confidence threshold for that provider family increases by 10 points (capped at 80) for 60 days And suggestions below the adapted threshold are not displayed Given a user has accepted a provider (e.g., Zendesk) When ranking future suggestions of the same family Then at least one suggestion of that family is boosted into the top 3 if available and within 5 points of the current top-3 boundary And the user can reset adaptation to defaults, after which the next discovery uses baseline thresholds
Service API Contract for Suggestions and Actions
Given an authenticated org-scoped token with suggestions:read When calling GET /v1/suggestions?provider_type=&cursor=&limit= Then the API returns 200 with items[] containing {id, provider, provider_type, confidence_score, rank, rationales[], required_scopes[], risk_level, latest_signal_timestamp, actions{accept,ignore,snooze}} And supports cursor pagination with limit<=100, returning next_cursor and approx_total And enforces tenancy: requests for other orgs return 403 And p95 latency is ≤ 300 ms for orgs with up to 100 suggestions and 1,000 evidence records Given an authenticated token with suggestions:write When calling POST /v1/suggestions/{id}/{accept|ignore|snooze} with Idempotency-Key Then the API is idempotent within 24 hours and returns the final state consistently
UI Components in Onboarding and Admin Dashboard
Given the onboarding flow and the admin dashboard When rendering the Source Radar component Then both use the same API responses and display consistent scores, rationales, scopes, risk, and actions And action state changes taken in one surface reflect in the other within ≤ 5 seconds And the list supports keyboard navigation (Tab/Shift+Tab), Enter to activate actions, and has ARIA roles/labels for assistive tech And on API failure, a non-blocking error banner with a Retry action is shown; no partial action is committed And if no suggestions are available, an empty state with guidance and a manual connect link is displayed
One-Click OAuth & Keyless Setup
"As a workspace admin, I want one-click connection flows with the correct scopes so that I can start ingesting feedback immediately and securely."
Description

Implements streamlined, least-privilege OAuth flows for Zendesk, Intercom, Jira, and Linear with pre-filled tenant identifiers and deep links to provider consent screens. Handles token exchange, secure storage, rotation, and error recovery. For app stores, guides admins through secure key-based setup (Apple App Store Connect API keys, Google Play service accounts) with validation and scope checks. Provides immediate connection testing and readiness status so ingestion can start within minutes.

Acceptance Criteria
Auto-Discovery Prefills Tenant Identifiers
Given a verified organization domain and SSO session When the admin opens Source Radar to connect Zendesk, Intercom, Jira, or Linear Then the tenant/account identifier field is pre-populated with the detected value and marked "verified" And the admin can edit or override the prefill before proceeding And invalid or ambiguous discoveries present a selectable list or a validation error with guidance
Least-Privilege OAuth Consent Flow for SaaS Sources
Given the admin clicks "Connect" for a provider When redirected to the provider consent screen Then the requested scopes are the minimum required for read-only ingestion and connection testing And the scope list displayed in EchoLens matches the provider's granted scopes on return And if scopes are denied or reduced, the connection is blocked with a message listing missing scopes and remediation
Secure Token Exchange, Storage, and Rotation
Given an authorization code is received from the provider When EchoLens exchanges the code for tokens Then the exchange occurs over TLS 1.2+ and tokens are stored encrypted using KMS with keys not accessible to application logs And refresh tokens are rotated proactively and verified by a successful refresh before expiry And if tokens are revoked at the provider, EchoLens updates the connection to "Requires Reauth" within 5 minutes and notifies the admin
Immediate Connection Test and Readiness Status
Given a new connection completes the OAuth or key-based setup When EchoLens returns from consent or validation Then a live minimal-permission API call is executed to verify access And the connection status updates to "Ready" on success or "Action Required" with specific failure reason and remediation steps on failure And 95% of connection tests complete within 10 seconds end-to-end
Resilient Error Handling and Recovery in Auth Flows
Given a transient provider or network error occurs during token exchange or readiness test When the error is classified as retryable (e.g., 429, 5xx, timeouts) Then EchoLens retries with exponential backoff up to 3 attempts before surfacing an error And user-facing errors include provider, failed step, and next action without exposing secrets And a successful retry sets the final connection status correctly without additional user action
App Store Key-Based Setup with Validation and Scope Checks
Given an admin supplies Apple App Store Connect (Issuer ID, Key ID, .p8) or Google Play service account JSON When the admin clicks "Validate" Then EchoLens verifies credential format, required roles/permissions for read-only access, and performs a live test API call And credentials are stored encrypted with KMS and redacted in the UI after save And insufficient permissions produce a failure listing missing roles/permissions with links to provider documentation
Deep Links to Provider Consent Screens with SSO Context
Given SSO context and detected tenant are available When the admin clicks "Connect" Then the provider consent screen opens with the correct tenant preselected and user session recognized And if SSO context is missing, a tenant selection step is shown before redirect And state/nonce and redirect URI validation prevents CSRF/open-redirect; mismatches block the flow with an error
Admin Overrides & Bulk Connect
"As an admin, I want to override suggestions and bulk-connect sources so that I can tailor setup to our multi-instance environment efficiently."
Description

Provides controls for admins to manually add, edit, or override detected sources, including entering subdomains, org IDs, and environment labels (production/sandbox). Supports bulk selecting and connecting multiple high-confidence suggestions in a single step. Performs validation checks (reachability, auth, permission scopes) and detects conflicts or duplicates (e.g., multiple Zendesk brands). Ensures multi-instance mapping with clear labeling and safe rollbacks.

Acceptance Criteria
Admin Manually Adds New Zendesk Source
Given I am an org admin on the Source Radar Overrides panel And I choose "Add Source" and select "Zendesk" And I enter subdomain "acme.zendesk.com", org ID "Z-123", and environment "production" When I click "Validate" Then the system verifies reachability over HTTPS within 10 seconds And verifies authentication using the provided OAuth/API token And confirms required scopes include read:tickets and read:users And displays pass/fail for each check with clear messages When I click "Connect" Then the source is created and appears in Connected Sources with label "Zendesk • acme (production)" And initial ingestion is queued within 30 seconds
Admin Edits Existing Source Configuration
Given an existing Intercom source is connected And I open its configuration in Overrides When I change the environment label from "sandbox" to "production" and update the subdomain And I click "Save" Then the system re-runs reachability, auth, and scope validation And if validation passes, the update is applied without creating a duplicate source And the connected source label updates to reflect the new environment And downstream mappings continue to use the same internal source ID And ingestion continues without data loss or duplication
Bulk Connect High-Confidence Suggestions
Given Source Radar displays auto-detected suggestions with confidence scores And at least three suggestions have confidence >= 0.80 When I select multiple suggestions and click "Connect Selected" Then each selected source is validated for reachability, auth, and permission scopes And sources that pass are connected; sources that fail remain unconnected And the results show per-source success or failure with actionable error messages And failures do not roll back successful connections And the batch completes with a visible summary (N succeeded, M failed)
Duplicate and Conflict Detection Across Multi-Instances
Given a Zendesk brand with org ID "Z-123" is already connected When I attempt to connect another source with the same vendor and org ID Then the system blocks the action and displays "Duplicate source detected" with a link to the existing source When I attempt to connect two Zendesk brands that map to the same org ID Then the system flags a conflict and requires me to resolve by choosing a unique mapping or label And the "Connect" action remains disabled until the conflict is resolved
Validation of Reachability, Auth, and Permission Scopes
Given I enter host and credentials for a Jira Cloud source When I click "Validate" Then the system confirms DNS resolves and TLS certificate is valid And performs an authenticated call to list accessible projects And reports any missing permission scopes explicitly (e.g., "read:jira-work") And no source record is created or updated if any validation check fails And the validation results return within 15 seconds
Safe Rollback on Failed Connect or Edit
Given a connect or edit operation is in progress for one or more sources When any step after validation fails (e.g., webhook creation, permission update) Then the system reverts all partial changes for the affected source(s) And removes any artifacts created during the failed attempt (tokens, webhooks, mappings) And preserves the last known good configuration for previously connected sources And displays a clear failure reason and a "Retry" option And no ingestion jobs run for any source that did not complete successfully
Clear Multi-Instance Labeling and Mapping
Given multiple instances of the same vendor are connected (e.g., Zendesk acme production, Zendesk acme sandbox, Zendesk globex production) When I view Connected Sources and selection menus Then each instance displays a unique label composed of vendor, instance identifier (subdomain/brand), and environment And labels are unique and unambiguous across all connected sources And each label maps to a unique internal source ID used for ingestion and sync And selecting a label consistently targets the correct instance in subsequent actions
Real-Time Rescan & Drift Detection
"As a product manager, I want Source Radar to automatically detect changes in our tool stack so that our connected sources stay accurate without manual upkeep."
Description

Runs scheduled and event-driven rescans to detect newly adopted tools or removed accounts by re-evaluating domain signals and SSO inventories. Avoids re-suggesting dismissed items, logs changes, and notifies admins of material updates. Maintains idempotent updates and respects provider rate limits. Keeps the Source Radar map current without manual intervention and ensures EchoLens ingests from the most relevant sources over time.

Acceptance Criteria
Scheduled Rescan Detects Newly Adopted Tools
Given an organization with connected domain(s) and SSO inventory snapshots And at least one new supported provider account (Zendesk, Intercom, Jira, Linear, App Store) has been adopted since the last scan When the scheduled rescan runs at its configured interval Then the system discovers the new account(s) with a non-zero confidence score And creates or updates discovery records with provider, account identifier, confidence score, first_seen timestamp, and discovery_source=scheduled And does not duplicate existing connected sources or suggestions And updates the Source Radar map to display the new candidate(s) within 5 minutes of scan completion And completes the rescan within 5 minutes for up to 10 providers and 10k SSO users
Event-Driven Rescan on SSO/App Change
Given a webhook or event from an SSO/provider indicating app install, account linking, or domain verification change When the event is received Then a targeted rescan is initiated within 60 seconds And newly eligible accounts are discovered and surfaced with confidence scores And unchanged accounts are left untouched (no-op) And the Source Radar map reflects changes within 2 minutes of rescan completion
Removal/Deprovision Detection and Ingestion Stop
Given a previously connected or suggested source is removed or deprovisioned in SSO or at the provider When a rescan evaluates current signals Then the source status transitions to Inactive within 5 minutes And downstream ingestion for that source is paused within 5 minutes without data loss And the change is captured with before/after status and timestamps And the Source Radar map updates to reflect the removal
Dismissed Suggestions Not Re-surfaced Without Material Change
Given an admin has dismissed a suggested source When subsequent rescans run Then the system does not re-suggest the same account identifier for 90 days unless a material change occurs And a material change is defined as account reactivation, a confidence score increase >= 20 points, or a new account identifier under the same provider And if a material change occurs, the suggestion reappears with reason="material_change"
Audit Log of Source Changes
Given any add, update, remove, connect, or dismiss action occurs via rescan or user action When the action completes Then an immutable audit event is recorded with correlation_id, actor (system/user), provider, account identifier, action, before/after values, timestamp (UTC), and triggering_source (scheduled|event-driven|manual) And audit events are queryable via API and UI with filters (action, provider, date range) for at least the last 180 days And fetching the most recent 100 events returns in under 2 seconds in the UI
Admin Notifications for Material Updates
Given a rescan results in material updates (new adoption, removal, or confidence delta >= 20 points) When the rescan completes Then admins subscribed to Source Radar notifications receive a single batched notification within 10 minutes for scheduled rescans and within 2 minutes for event-driven rescans And notifications are de-duplicated across channels (email, in-app) And the notification includes a summary count, provider names, account identifiers, change types, and deep links to the Source Radar screen And notification preferences (channel, frequency, mute) are respected
Idempotent Updates and Provider Rate-Limit Compliance
Given repeated identical rescans over the same inventory When the rescan executes multiple times Then the resulting Source Radar state remains unchanged (no duplicate suggestions, no flapping statuses) And applying the same connection or dismissal action twice does not create duplicates or errors And API calls to external providers remain within documented rate limits, using backoff and retries on 429/5xx And during a load test of 1,000 organizations, fewer than 1% of provider calls are retried due to rate limiting, and no rescan fails solely due to rate limits
Privacy, Consent, and Audit Logging
"As a security-conscious admin, I want clear consent and comprehensive auditing for discovery and connections so that our compliance and privacy requirements are met."
Description

Introduces explicit pre-connection consent screens outlining data categories and requested scopes for each provider. Defers any credential storage until consent is granted and encrypts sensitive material at rest and in transit. Ensures tenant isolation, SOC 2-aligned access controls, and configurable data retention for discovery artifacts. Captures immutable audit logs of discovery events, admin actions, consents, and connection changes for compliance reviews.

Acceptance Criteria
Pre-Connection Consent Gate for Discovered Providers
Given a provider account is auto-discovered via Source Radar and mapped to the tenant, When a user initiates connection, Then the consent screen is shown before any token exchange that would grant persistent access. Given the consent screen is displayed, When the user selects Decline, Then no connection record is created and no credentials are stored or retrievable. Given the consent screen is displayed, When the user selects Accept, Then a connection record is created only after consent and the consent decision is saved to the audit log with provider, scopes, actor, tenant, and timestamp.
Granular Scope Disclosure and Selection
Given a consent screen for a provider, When it renders, Then it lists all requested scopes and data categories in plain language with purpose statements and marks which are required versus optional. Given optional scopes are deselected by the user, When the connection is created, Then the resulting provider token has only the selected scopes as verified by provider API introspection or a test call, and access to unselected categories is blocked. Given the application needs additional scopes later, When scopes change, Then the user is re-prompted for consent and the connection operates with prior scopes until consent is granted; scope elevation without re-consent is blocked and logged.
Deferred Credential Storage and Ephemeral Handling
Given the OAuth or device flow is initiated, When consent has not yet been granted, Then no refresh tokens, access tokens, secrets, or API keys are persisted to any storage. Given a user cancels or times out before granting consent, When any ephemeral tokens were obtained, Then they are revoked within 60 seconds and are not written to logs or metrics; a decline event is audited. Given consent is granted, When credentials are stored, Then they are saved only in the designated secrets store with write confirmation, and any previous ephemeral artifacts are securely wiped from memory and disk.
Encryption and Secure Transport for Secrets
Given any sensitive credential or discovery artifact is stored, When written at rest, Then it is encrypted using AES-256-GCM (or provider-managed equivalent) with keys managed by KMS and rotated at least every 90 days. Given any credential is transmitted between services, When sent over the network, Then TLS 1.2+ is enforced with HSTS and certificate pinning in clients where supported. Given application logs or error messages are produced, When they include fields matching secret patterns, Then secrets are redacted and never logged in plaintext; automated tests verify zero occurrences in log sinks.
Tenant Isolation and SOC 2–Aligned Access Controls
Given multi-tenant operation, When accessing discovery artifacts or credentials, Then a tenant identifier is required and enforced at the data layer; cross-tenant access attempts return 403 and are audited. Given internal staff need to view discovery artifacts, When access is requested, Then just-in-time access with manager approval, time-bound expiry of 2 hours or less, and least-privilege scopes is required. Given RBAC policies are configured, When a role is updated, Then changes take effect immediately, are versioned, and are recorded in the audit log with before/after diffs.
Immutable Audit Logging of Discovery and Consent Lifecycle
Given any of the following events occur—discovery detections, consent views and decisions, connection create/update/delete, scope changes, admin access—When the event is committed, Then an audit entry is appended with ISO 8601 UTC timestamp, actor, tenant ID, provider, action, and hashes of prior/new values. Given audit storage, When entries are written, Then they are append-only and tamper-evident via hash chaining and write-once object storage; attempts to modify or delete entries are blocked and audited. Given an auditor queries the last 90 days, When filtering by tenant and provider, Then results return within 2 seconds for up to 10,000 events and include verifiable integrity proofs.
Configurable Data Retention for Discovery Artifacts
Given a tenant retention policy is set between 30 and 365 days with a default of 180 days, When artifacts reach end-of-life, Then they are purged within 24 hours and a purge event is written to the audit log including counts and identifiers hash. Given a legal hold is applied by an authorized role, When retention would expire, Then artifacts under hold are not deleted until the hold is lifted; all holds and releases are audited. Given retention configuration is changed, When saved, Then the change is validated, versioned, applied to TTL/index policies within 15 minutes, and prior artifacts honor the new schedule.

Smart Mapper

Intelligently pre‑maps fields, tags, and custom attributes across systems, with confidence scores and one‑tap confirm. Aligns users, tickets, reviews, and projects without spreadsheets, preventing misrouted data and bad dashboards from day one.

Requirements

Auto Schema Discovery & Mapping
"As a product manager connecting a new source, I want EchoLens to auto-discover fields and propose mappings so that I can go live without manual spreadsheets."
Description

Automatically detects fields, tags, and custom attributes from connected sources (e.g., Jira, Linear, Zendesk, Intercom, email, app store reviews) and proposes mappings to EchoLens’ canonical entities (User, Ticket, Review, Project, Message). Uses a combination of heuristics, NLP, and metadata inspection to infer intent (e.g., reporter vs assignee, environment vs device). Produces suggested mappings with rationales and integrates into the ingestion pipeline so accepted mappings are applied immediately. Reduces setup time and prevents misrouted data by providing sensible defaults that can be confirmed or overridden.

Acceptance Criteria
Initial Multi-Source Auto-Discovery and Mapping Proposal
Given a workspace connects Jira, Linear, Zendesk, Intercom, IMAP email, and App Store Reviews When schema discovery is executed Then fields, tags, and custom attributes from each source are enumerated within 2 minutes per source for datasets up to 10,000 fields And each proposed mapping to EchoLens entities (User, Ticket, Review, Project, Message) includes a confidence score between 0.0 and 1.0 and a human-readable rationale And duplicate attributes across sources are normalized and de-duplicated (e.g., same email maps to a single User) with one canonical proposal And unsupported or unreadable fields are flagged with a reason and excluded from auto-mapping And the discovery run is idempotent, producing identical proposals on repeat runs when sources are unchanged
One-Tap Confirm Activates Mappings in Ingestion
Given a user selects one or more suggested mappings with confidence >= 0.80 and taps Confirm When the ingestion pipeline runs Then the selected mappings are applied within 30 seconds and used to transform all new incoming data And the user can trigger a one-time reprocess of historical data and see a summary of items remapped by entity type And each mapping application success or failure is logged with timestamp and error details on failure And failed applications are auto-retried up to 3 times with exponential backoff and surfaced in the UI
Ambiguity Handling and Safe Defaults
Given suggested mappings with confidence < 0.50 or with conflicting candidates When the user reviews them Then they are marked Needs Review and are not auto-applied And a preview shows at least 10 sample records for each candidate mapping And choosing Skip keeps the source field unmapped and routes data to an Unmapped bucket without loss And conflicts must be resolved before confirmation; partial confirmation applies only to non-conflicting items
Intent Inference Quality (Reporter vs Assignee; Environment vs Device)
Given a labeled validation set of >= 500 examples per intent pair across connected sources When intent inference runs Then macro-F1 >= 0.85 for Reporter vs Assignee and >= 0.85 for Environment vs Device overall And per-source macro-F1 is reported and is >= 0.80 for each source with >= 200 examples And at least 20 misclassified examples per intent pair are exportable for review And changing source language to French or Spanish yields overall macro-F1 >= 0.80 on the same test size
Schema Drift Detection and Mapping Maintenance
Given a connected source adds, renames, or deprecates fields, tags, or custom attributes When the next sync occurs Then new items are discovered and proposed within 15 minutes with confidence scores and rationales And likely renames are flagged with a Proposed Rename linking old to new with confidence >= 0.70 And deprecated items retain their last-known mapping but are marked Deprecated and require confirmation before disabling And admins receive a notification summarizing changes and required actions
Overrides, Versioning, and Rollback
Given a user overrides a suggested mapping or creates a manual mapping When they save the change Then a new mapping version is created with author, timestamp, and change description And rollback to any previous version is available and applies within 30 seconds And an audit log records before/after values and count of affected entities during next ingestion And a Test on Sample action validates the mapping on a 100-record sample without affecting production
Confidence Scoring & Thresholds
"As a CX lead, I want confidence scores with adjustable thresholds so that low-confidence mappings are queued for review and only reliable ones auto-apply."
Description

Generates confidence scores for each suggested mapping based on feature signals (naming similarity, historical confirmations, value distributions) and exposes configurable thresholds per source and entity type. High-confidence suggestions auto-apply; medium-confidence items route to review; low-confidence items are held for manual mapping. Displays score explanations to build trust and enables admins to tune aggressiveness by workspace. Ensures safe automation while keeping humans in the loop where risk is higher.

Acceptance Criteria
Confidence Score Calculation Uses Feature Signals
Given a suggested mapping candidate for a specific source and entity type in a workspace When the system computes the confidence score Then the score is a numeric value between 0 and 100 inclusive And the calculation incorporates available signals: naming similarity, historical confirmations, and value distribution similarity And if any signal is unavailable, the score is computed using the remaining signals without error and the weighting is normalized to sum to 1 And the same inputs produce the same score across runs for the same scoring model version And the score metadata includes the scoring model version and the contributing signals used And the p95 computation time per suggestion is <= 300ms for a batch of 1,000 suggestions
Auto-Apply High-Confidence Mappings
Given configured thresholds for a source and entity type in a workspace where High = 80 and Medium = 50 And a suggested mapping with a computed score >= High When the suggestion is generated or re-evaluated Then the mapping is auto-applied without user intervention And the item is marked "Applied (Auto)" and removed from any review queues And an entry is recorded containing score, thresholds used, decision = auto-apply, source, entity type, timestamp, and actor = system And the UI reflects the applied state within 2 seconds of score availability
Route Medium-Confidence to Review Queue
Given configured thresholds for a source and entity type in a workspace where High = 80 and Medium = 50 And a suggested mapping with Medium <= score < High When the suggestion is generated or re-evaluated Then the suggestion appears in the Review queue labeled "Needs Review" And clicking Confirm applies the mapping immediately and records actor = current user And clicking Dismiss removes the suggestion without applying a mapping and records actor = current user And no auto-apply occurs while in this state
Hold Low-Confidence for Manual Mapping
Given configured thresholds for a source and entity type in a workspace where High = 80 and Medium = 50 And a suggested mapping with score < Medium When the suggestion is generated or re-evaluated Then the item is placed in the Manual Mapping list with state = "Manual Required" And no auto-apply or review-queue routing occurs And the user can open a manual mapping dialog to create or select a mapping explicitly And the system records that the suggestion was held due to low confidence with the score and thresholds used
Threshold Configuration and Validation per Source and Entity Type
Given an admin user in a workspace When the admin opens Threshold Settings and selects a specific source and entity type Then the admin can set High and Medium thresholds as numeric values between 0 and 100 inclusive And validation prevents saving unless High > Medium And invalid inputs show inline error messages and disable the Save action And saving persists the thresholds scoped to the selected source, entity type, and workspace And if no specific thresholds are set, the effective thresholds fall back to workspace defaults; if none, to system defaults
Score Explanation Display for Suggested Mapping
Given a suggested mapping with a computed confidence score and thresholds in a workspace When the user clicks "Why this score?" Then the UI displays the total score, the current High and Medium thresholds, and the resulting confidence band (High/Medium/Low) And it lists each contributing signal (naming similarity, historical confirmations, value distribution similarity) with its normalized contribution and brief description And it includes a one-line plain-language summary (e.g., "High naming similarity and 12 prior confirmations") And the explanation can be copied or expanded without leaving the screen
Reprocessing After Threshold Change Applies New Decisions
Given a workspace where an admin updates thresholds for a specific source and entity type When the admin saves the new thresholds Then all non-finalized suggestions for that source and entity type are re-evaluated within 10 seconds p95 And items with score >= new High are auto-applied, items with score between new Medium and new High move to Review, and items with score < new Medium move to Manual Required And a summary banner reports counts of Auto-applied, Moved to Review, and Held for Manual And the decision records include the thresholds version applied at the time of re-evaluation
Cross-System Identity Resolution
"As a data admin, I want identities from different tools resolved into a unified profile so that tickets, reviews, and projects link to the right customer."
Description

Unifies user and account identities across systems by matching emails, external IDs, domain heuristics, and profile attributes to create a single EchoLens profile. Supports deterministic and probabilistic matching with explainable scores and conflict resolution rules. Handles duplicates and merges with auditability, and propagates resolved identities to linked tickets, reviews, chats, and projects. Prevents fragmented analytics and misattribution by ensuring all artifacts align to the correct user or account.

Acceptance Criteria
Deterministic Match: Email and External ID
Given records from multiple systems share the same normalized email or the same external_id, When the resolver executes, Then a single EchoLens profile is created or selected and all records are linked to it, And confidence_score = 1.0 with reason = "deterministic:email" or "deterministic:external_id", And no duplicate EchoLens profiles are created for that identity in the same run, And all associated artifacts (tickets, reviews, chats, projects) reference the unified profile.
Probabilistic Matching with Thresholds and Explainability
Given candidate records share partial attributes (e.g., name similarity, domain, profile attributes), When the resolver computes a confidence score, Then matches with score >= 0.90 are auto-linked with reason = "probabilistic:auto", And matches with 0.70 <= score < 0.90 are placed in a review queue with one-tap confirm or deny, And matches with score < 0.70 are not linked, And an explanation is stored listing top contributing features with weights, accessible via UI and API.
Conflict Resolution and Precedence Rules
Given multiple candidate profiles compete for the same record, When conflict resolution runs, Then precedence is applied in this order: external_id > exact email > verified domain > probabilistic score, And ties are broken by highest score then most recent activity date, And manual override by an authorized user selects the winner and persists as a rule, And an audit log entry records actor, timestamp, old->new mapping, reason, and previous state, And a revert action restores the prior mapping without data loss.
Duplicate Detection and Safe Merge
Given two or more EchoLens profiles are determined to represent the same identity, When a merge is initiated, Then a winner profile is selected per rules and loser profiles are marked merged_into = winner_id, And field-level merge strategy is applied (prefer non-null, latest updated, or rule-defined), And all child artifacts are re-pointed to winner with zero orphans, And the merge operation is idempotent and re-running produces no additional changes, And an audit record contains before/after snapshots and merge rationale.
Account Resolution via Domain Heuristics
Given users share the same corporate email domain not in the free-email denylist, When account resolution runs, Then they are grouped under a single Account profile unless an allowlisted exception specifies otherwise, And domains on the denylist (e.g., gmail.com) never auto-create accounts, And verified domains from CRM take precedence over inferred domains, And confidence_score and reason are stored for the account link decision.
Propagation and Latency SLAs
Given an identity is created, merged, or remapped, When propagation executes, Then all linked artifacts in EchoLens reflect the change within 5 minutes, And downstream syncs to Jira/Linear include the resolved EchoLens user/account IDs within 5 minutes, And no artifact remains linked to a deprecated profile after propagation completes, And a monitoring metric reports success rate >= 99.9% over 24h and alerts on breaches.
Tag & Attribute Harmonization to Themes
"As a product analyst, I want tags and custom fields harmonized to our canonical themes so that dashboards and the live theme map stay accurate and comparable."
Description

Maps heterogeneous tags, labels, and custom fields from each source to EchoLens themes and standardized attributes using synonym dictionaries and embedding-based similarity. Surfaces proposed merges and splits for near-duplicate labels (e.g., "perf" vs "performance") and aligns them to the live theme map. Supports organization-specific vocabularies and exceptions. Ensures consistent analytics and ranked queues by eliminating semantic drift across tools.

Acceptance Criteria
Initial Ingest Auto-Mapping with Confidence Thresholds
Given at least one source (e.g., Zendesk, Intercom, App Store, Jira) is connected and ingest is initiated When tag/label/custom field values are processed Then mappings with confidence >= 0.90 are auto-applied to a single EchoLens theme and standardized attribute And mappings with confidence between 0.70 and 0.89 are queued for one-tap confirmation And mappings with confidence < 0.70 remain unmapped with a reason code And p95 per-item mapping latency <= 150 ms and p99 <= 400 ms during ingest And a coverage report (auto-applied %, queued %, unmapped %) is generated within 60 seconds of ingest start
Near-Duplicate Tag Merge and Split Suggestions
Given a dataset containing near-duplicate labels across sources When synonym dictionaries and embedding similarity are evaluated Then merge proposals are generated for pairs with similarity >= 0.85 or dictionary synonym match And auto-apply is disabled unless confidence >= 0.95 and estimated impact < 5% of items And on a labeled validation set, precision of merge proposals >= 98% and recall >= 85% And upon confirming a merge, historical backfill updates affected items (up to 5M) within 15 minutes and analytics reflect changes within 2 minutes after backfill completion And split proposals are generated when a single label clusters into >= 2 distinct groups (silhouette >= 0.5) and each target theme has >= 30% share; confirmation triggers equivalent backfill SLAs And each proposal displays at least 3 representative examples per target mapping in the review queue
Org-Specific Vocabulary Overrides and Exceptions
Given an organization admin defines a custom synonym, canonical mapping, or do-not-merge exception When harmonization runs or is re-run after a model update Then the override rules are applied 100% of the time and take precedence over model suggestions And overrides are namespaced to the organization with no cross-tenant leakage And all overrides are versioned with user, timestamp, rule type, and change notes recorded in the audit log And overrides persist across re-ingest and model updates without manual re-entry And a test suite can export and re-import override rules with lossless parity
Live Theme Map and Analytics Consistency After Harmonization
Given a merge, split, or override is confirmed When the change is applied Then the live theme map updates within 10 seconds And dashboards and ranked queues reflect the new mapping within 60 seconds And no items are orphaned; total item counts before vs. after are equal (±0) And downstream integrations (Jira/Linear sync) use updated attributes for new items within 30 seconds And a before/after diff report is available for the affected themes with item deltas and timestamps
Cross-Source Label Alignment and Drift Monitoring
Given harmonization has completed across at least two sources When evaluating a random stratified sample of 500 items per source Then conceptually equivalent labels map to the same theme with mismatch rate <= 2% And cross-source coverage (items mapped to a theme) >= 95% And a daily drift job flags any emergent unmapped tag reaching >= 0.5% of daily volume, creating an alert with a suggested mapping within 1 hour And a weekly report lists top 20 unmapped/low-confidence labels with volumes and suggested actions
Safety, Auditability, and Rollback of Mapping Changes
Given any mapping change (auto-applied or confirmed) When the change is recorded Then an immutable audit entry is written with actor (user/system), rule id, confidence, before/after theme(s), scope, and item counts And the system supports undo of any individual change or batch by id, with full rollback of mappings, analytics, and theme map within 60 seconds And high-impact changes (estimated to affect > 5% of total items or > 10,000 items) require explicit approval and cannot auto-apply And all mapping operations are idempotent and retried on transient failures with exponential backoff (max 3 retries) and alert on exhaustion
One-Tap Review, Bulk Confirm, and Overrides
"As a product manager, I want a one-tap review queue with bulk confirm and overrides so that I can quickly approve mappings and correct edge cases."
Description

Provides a dedicated review workspace showing suggested mappings with context, confidence, and diffs. Enables one-tap confirm, reject, or edit, plus bulk actions filtered by source, entity, and confidence band. Supports quick creation of custom rules and overrides, with preview and impact estimates before applying. Records all actions to an audit trail and allows rollback of recent changes. Streamlines human-in-the-loop validation while safeguarding against errors.

Acceptance Criteria
One-Tap Confirm: Single Mapping Approval
Given I am in the review workspace viewing a suggested mapping with context, confidence score, and diffs When I tap Confirm Then the mapping is saved and marked as Confirmed And the item is removed from the review queue And the affected entity reflects the new mapping immediately And an audit entry records user, timestamp, before/after diffs, confidence at time of action, and source And a success notification appears within 1 second And the action completes within 400 ms at the 95th percentile
One-Tap Reject: Single Mapping Dismissal
Given I am viewing a suggested mapping When I tap Reject and optionally provide a reason Then the suggestion is dismissed and excluded from the queue And the underlying entity remains unchanged And an audit entry records user, timestamp, reason (if provided), and suggestion metadata And the same suggestion is not re-surfaced for 7 days unless its inputs change And the action completes within 400 ms at the 95th percentile
Inline Edit with Preview and Confirm
Given I open Edit on a suggested mapping When I modify fields/tags/custom attributes and click Preview Then I see a diff view and an impact estimate including count of records affected and confidence distribution When I click Apply and Confirm Then the updated mapping is saved And the realized impact matches the preview within ±1% And all changes are captured in the audit trail And I can undo the change via Rollback
Create Override Rule with Impact Estimate
Given I choose Create Rule/Override from a suggestion When I define rule conditions (source, entity type, attribute pattern, confidence threshold) and click Preview Then I see the number of suggestions and records the rule will affect and representative examples When I click Apply Then the rule is stored, versioned, and immediately applied to the queue And impacted items are updated per rule within 30 seconds for up to 10,000 items And an audit entry logs rule definition, author, version, scope, and counts changed And I can disable or rollback the rule to revert its effects
Bulk Confirm/Reject via Filtered Selection
Given I filter suggestions by source, entity type, and confidence band When I select all results (up to 1,000 items) and choose Confirm or Reject Then I see a bulk action summary with counts and estimated impact When I proceed Then at least 99% of selected items are processed successfully And failures are reported with reasons and can be retried And the queue and affected entities reflect the changes within 30 seconds And a consolidated audit record references the bulk operation with per-item entries
Audit Trail Visibility and Rollback of Recent Changes
Given confirm, reject, edit, bulk, or rule-apply actions have been performed When I open the Audit Trail and filter by user, action type, entity, or time range Then I can view entries with user, timestamp, before/after, source, confidence at action, and correlation IDs And I can rollback any single action or bulk operation executed in the past 24 hours Then the targeted entities revert to their prior state, and a new audit entry records the rollback And any derived rules disabled by rollback are versioned and marked as rolled back And rollbacks complete within 30 seconds and are idempotent
Continuous Learning, Drift Detection, and Alerts
"As a data steward, I want the mapper to learn from my feedback and alert me to schema drift so that data remains correctly aligned over time."
Description

Continuously learns from user confirmations and overrides to improve future suggestions. Monitors source schemas and value distributions to detect drift (new fields, renamed labels, changing formats) and triggers alerts with recommended remappings. Version-controls mapping configurations, supports safe rollouts, and offers environment-scoped testing before production. Maintains long-term alignment and minimizes ongoing maintenance burden.

Acceptance Criteria
Learning from User Confirmations and Overrides
Given at least 50 user-confirmed or overridden mappings exist for a source→target pair in the last 7 days When the Smart Mapper performs its next incremental update Then top-1 suggestion accuracy on a blinded holdout of the last 200 mapping candidates increases by ≥5% or remains ≥85% And expected calibration error (ECE) of confidence scores is ≤0.10 Given a user overrides a suggested mapping for a specific source value When the same source value reappears after model update (≤1 hour) Then the system suggests the overridden mapping with confidence ≥ the previous suggestion’s confidence Given a mapping suggestion has been one-tap confirmed ≥3 times for an identical candidate When the candidate reappears Then the system auto-applies the mapping if confidence ≥0.90 and logs the auto-apply action with traceability to the confirmations
New Field Drift Detection and Recommended Mapping
Given a connected source introduces a new field not present in the last known schema When the next schema poll runs (every 15 minutes) Then a drift event of type "New Field" is created within 15 minutes of discovery And the drift event lists field name, sample values (≥5), and source metadata And at least one recommended target field mapping is generated with confidence ≥0.70 or the system explicitly marks "No confident suggestion" And the new field is actionable via one-tap confirm to apply the recommendation
Value Distribution/Format Drift Detection
Given a mapped field’s incoming value distribution changes beyond thresholds When hourly drift detection runs Then a drift event is created if any condition is met: missing rate increases by ≥10 percentage points vs prior 7-day baseline, KL divergence ≥0.30, or parse error rate ≥2x baseline And the event includes the list of impacted mappings, before/after summaries, and top 10 shifted values/examples And recommended remappings are proposed for impacted values with confidence ≥0.60, or the system flags as "Needs review" if confidence <0.60
Alert Delivery, Consolidation, and Deduplication
Given one or more drift events are created for the same source within a 30-minute window When alerts are dispatched Then a single consolidated alert is sent per source with counts by drift type And alerts are delivered to in-app, email, and Slack (if configured) within 5 minutes of consolidation And duplicate alerts for the same unresolved drift cluster are suppressed for 30 minutes And acknowledging the alert in any channel updates its status to "Acknowledged" across all channels within 2 minutes And resolving the underlying drift automatically closes the alert within 2 minutes
Version-Controlled Mapping Configurations and Rollback
Given any mapping configuration is created, edited, or deleted When the change is saved Then a new immutable version is recorded with author, timestamp, change reason, diff of affected mappings, and related drift event IDs And versions are diffable in UI and exportable as JSON And rollback to any previous version is possible with one tap and completes within 2 minutes And rollback creates a new version entry capturing the revert with full audit trail
Safe Rollouts via Canary with Auto-Rollback
Given a new mapping configuration version is ready to deploy When the user selects Canary rollout Then the system deploys to a canary environment or 10% of traffic for selected sources And it monitors mapping error rate and override rate for at least 1,000 events or 2 hours (whichever comes first) And the system auto-rolls back if error rate increases by ≥5 percentage points vs baseline or override rate ≥2x baseline And upon user approval, promotion to 100% completes within 5 minutes and creates a new version entry
Environment-Scoped Testing and Simulation Gate
Given a user opens a change set for mappings When they run Simulation in a non-production environment using the last 7 days of sampled data (≥10,000 events or full volume if smaller) Then the system produces an impact report including: expected auto-apply rate, conflicts, affected targets/projects, and dashboard impact summary And no production writes occur during simulation And promotion to production is blocked until the simulation completes with zero critical conflicts and at least one Reviewer approves
Historical Backfill & Real-Time Sync Application
"As an operator, I want mappings applied to historical and incoming data with clear progress so that my reports are consistent immediately and stay that way."
Description

Applies confirmed mappings to both historical data (backfill) and new ingestions in real time. Provides resumable jobs with progress, rate-limit compliance, and failure retries across sources. Shows impact metrics (records updated, themes merged) and ensures idempotent application to avoid duplication. Guarantees that dashboards and ranked queues reflect consistent mappings from day one onward.

Acceptance Criteria
Resumable Backfill Job with Progress Persistence
- Given a historical backfill job is running and the worker is terminated unexpectedly; When the job is resumed; Then processing restarts from the last successfully committed record (no reprocessing), and the progress indicator resumes from the last saved percentage (±1%). - Given a running backfill; When the user clicks Pause; Then the job transitions to Paused within 5 seconds and can be resumed to completion with no data loss. - Given a multi-source backfill (e.g., Zendesk and Intercom); When one source encounters an error; Then other sources continue, and the failed source is resumable independently from its last checkpoint.
Rate-Limit Compliance Across Sources
- Given configured API rate limits for each source; When backfill or sync executes; Then request rates per source never exceed configured ceilings and do not trigger 429s under nominal conditions. - Given a 429 response or Retry-After header is received; When handling the request; Then the client backs off per header value with jitter and retries without violating limits. - Given rate-limit settings are changed during a run; When the configuration is updated; Then the limiter adapts within 60 seconds and new limits are honored.
Idempotent Mapping Application
- Given confirmed mappings exist; When the same dataset is backfilled multiple times; Then no duplicate records, links, or theme merges are created and the “records updated” count remains unchanged on reruns. - Given a previously mapped record is re-ingested in real time without mapping changes; When processed; Then the operation is a no-op and no new events are emitted. - Given a mapping version is incremented for a record; When the job is rerun; Then only delta changes are applied once and the audit shows a single application per record id+version.
Real-Time Sync Latency and Ordering Guarantees
- Given new data arrives from connected sources; When real-time sync processes events; Then 95th percentile end-to-end mapping latency is ≤ 60s and 99th percentile ≤ 120s under expected load. - Given multiple updates for the same record arrive in quick succession; When processed; Then last-write-wins based on event timestamp is enforced and earlier updates are superseded. - Given a temporary connectivity loss up to 5 minutes; When connectivity is restored; Then the backlog is drained and latency SLOs are re-attained within 10 minutes.
Failure Handling, Retries, and Dead-Lettering
- Given transient failures (HTTP 5xx, timeouts, network resets); When they occur; Then the system retries up to 5 times with exponential backoff (base 2) and successful attempts are deduplicated. - Given permanent failures (4xx excluding 429); When they occur; Then the record is marked failed without further retries, the error is logged with source, code, and message, and appears in the job report. - Given failures exceed a threshold of 1% of processed records; When the job completes; Then the job status is Warning, a dead-letter queue contains failed record IDs, and a “Retry failed only” action is available.
Impact Metrics and Dashboard/Queue Consistency
- Given a backfill or sync run completes; When the user opens the Job Summary; Then metrics display total scanned, records updated, themes merged/split, duration, and per-source breakdown with ±1% accuracy. - Given mappings have been applied; When the user loads dashboards and the ranked queue; Then all counts, filters, and rankings reflect the mappings consistently within 2 minutes and contain no duplicates or unmapped items. - Given the user requests verification; When sampling; Then at least 20 audit samples per source are available linking to source IDs, applied mapping values, and confidence scores.

Webhook Auto‑Heal

Continuously verifies and re‑registers webhooks if they’re revoked, expire, or drift. Detects missed events and backfills safely, closing hidden sync gaps so triage and scoring stay accurate in real time.

Requirements

Webhook Health Check & Drift Detection
"As a CX lead, I want EchoLens to automatically detect when a source’s webhooks become unhealthy so that our live theme map and prioritization queue don’t drift due to silent data loss."
Description

Continuously validates webhook registrations across all connected sources (e.g., Intercom, Zendesk, Help Scout, App Store Connect) to detect revocations, expirations, URL drift, missing scopes, signature mismatches, and elevated 4xx/5xx error rates. Uses provider APIs to enumerate active subscriptions, verifies callback endpoints and shared secrets, and (where supported) performs synthetic handshake checks. Maintains a per-source health state and triggers the auto-heal workflow when anomalies are detected, pausing ingestion for unsafe feeds to prevent stale or misleading insights. Seamlessly integrates with EchoLens’s ingestion orchestrator so the live theme map and ranked queue remain accurate, minimizing hidden data gaps that would skew triage and scoring.

Acceptance Criteria
Provider Subscription Enumeration and Mismatch Detection
Given a connected source with valid provider credentials and an expected webhook specification When the health-check scheduler runs the subscription enumeration task Then the system calls the provider’s list-subscriptions API within configured rate limits and completes within the configured timeout And the system diffs provider state against the expected spec (missing/extra subscriptions, event-type drift, target URL mismatch, delivery format mismatch) And each discrepancy is recorded with normalized reason codes and machine-readable details And health is set to Unhealthy if any critical discrepancy exists (missing required subscription or target URL mismatch), else Degraded if only non-critical discrepancies exist And enumeration_count and discrepancy_count metrics are emitted and an audit entry is written
Revocation and Expiration Detection
Given a provider that exposes webhook status or expiration metadata When any subscription is detected as revoked/disabled or expiring within the configured threshold window Then the source health is set to Unhealthy for revoked/disabled and Degraded for expiring-soon And an Auto-Heal workflow trigger is emitted with source_id, provider, anomaly_type, and current_state And the trigger is idempotent within the configured cooldown window and no secret values are logged And an audit event is recorded with timestamp and reason code
Callback URL Drift and Signature Verification
Given an expected callback URL and shared secret stored in the secrets manager When the registered callback URL at the provider differs from the expected value Then health is set to Unhealthy and an Auto-Heal trigger is emitted to re-register the correct URL Given recent deliveries exist or a provider test-delivery is available When the system validates signatures/HMAC of the last N (configured) deliveries Then all N signatures validate against the stored secret within the verification timeout Else health is set to Unhealthy, secret rotation is requested via Auto-Heal, and ingestion for the source is paused until signatures validate And secrets are never logged or exposed in events or metrics
Scope and Permission Validation
Given an OAuth connection to a provider with a defined required-scope set When the system queries granted scopes/permissions for the connection Then the granted set is compared to the required set deterministically And if any required scope is missing or expired, health is set to Unhealthy and an Auto-Heal trigger is emitted to recover scopes or prompt re-auth And ingestion for that source is paused until required scopes are present And an audit entry and missing_scope_count metric are recorded
Delivery Error-Rate Anomaly Detection
Given delivery outcomes are tracked per source in a rolling window When the 15-minute 4xx rate is >= 5% with at least 10 deliveries or there is a streak of >= 5 consecutive 4xx responses Then health is set to Degraded When the 15-minute 4xx rate is >= 15% or there is a streak of >= 10 consecutive 4xx responses Then health is set to Unhealthy and ingestion for the source is paused When the 15-minute 5xx rate is >= 1% with at least 5 deliveries or there is a streak of >= 3 consecutive 5xx responses Then health is set to Unhealthy and ingestion is paused And in all cases an Auto-Heal trigger is emitted once per anomaly type per cooldown and metrics (error_rate_4xx, error_rate_5xx, streak_max) are updated
Synthetic Handshake Probe (Provider-Supported)
Given a provider that supports verification challenge, ping, or test-delivery When the synthetic handshake probe runs at its configured cadence Then the probe executes within rate limits and timeout budgets and expects a 2xx response with valid challenge/verification and signature Else health is set to Unhealthy and an Auto-Heal trigger is emitted Given a provider marked as not supporting probes in the capability registry When the probe scheduler evaluates the source Then the probe is skipped and lack of probe support does not negatively impact health
Health State Transitions, Auto-Heal Idempotency, and Ingestion Gating
Given an anomaly is detected by the health checker When a state transition is warranted Then the per-source health moves only among Healthy, Degraded, and Unhealthy using deterministic rules and timestamps are recorded And an Auto-Heal workflow is triggered with an idempotency key composed of source_id + anomaly_type + hash(details) and duplicates within cooldown are suppressed And the ingestion orchestrator receives a pause gate for Unhealthy sources and a resume signal upon return to Healthy/Degraded according to policy And paused sources contribute no new data to the theme map or scoring, and resume restores normal flow And the impacted time interval is marked for backfill eligibility and an audit trail captures all actions
Auto Re‑registration & Secret Rotation
"As a product operations owner, I want webhooks to re-register and rotate secrets automatically when they break so that data continues to flow without manual firefighting."
Description

Automatically re-registers affected webhooks with least-privilege event scopes when health checks fail, rotating verification secrets/tokens and updating provider-side configurations as needed. Performs immediate post-registration validation (e.g., challenge/response or signature verification), rolls back on failure, and records changes in an auditable log. Stores new secrets in a secure KMS with versioning and supports provider-specific flows (e.g., app reinstall, consent refresh). Includes jittered retries and guardrails to avoid thrashing providers. Keeps ingestion streams live with minimal operator intervention, preserving real-time accuracy for theme clustering and the ranked action queue with one-click Jira/Linear sync.

Acceptance Criteria
Revoked Webhook Auto Re-Registration
Given a webhook health check detects revocation, expiration, 401/403/410, or invalid signature When the failure is detected Then the system re-registers the webhook with the minimal event scopes required by current EchoLens subscriptions within 120 seconds of detection And a new verification secret/token is generated And the secret/token is stored as a new version in KMS and referenced only by version ID And provider challenge/response or signature verification succeeds within 30 seconds of registration And provider-side configuration (endpoint URL, secret, scopes) is updated to match the new registration And the webhook is marked healthy and resumes ingestion And an audit log entry is created within 5 seconds with correlation_id, provider, integration_id, old/new endpoint identifiers, scopes, secret_version_id, outcome=success
Secret Rotation & KMS Versioning
Given a re-registration is initiated or the current secret will expire within 3 days per provider metadata When rotation is triggered Then a new secret version is created in KMS and set active while the previous version remains valid for up to 15 minutes for dual verification And no plaintext secrets are written to logs, configs, or database And access to the secret is limited to the ingestion service role as enforced by KMS policies And after successful validation of the new secret, the previous version is revoked within 5 minutes and its status recorded in the audit log And all secret reads are performed via KMS API calls with audit trails
Provider Reinstall/Consent Refresh Flow
Given the provider API returns uninstall, insufficient_scope, invalid_grant, or consent_required during re-registration When this condition is detected Then retries are paused and a consent/reinstall task is created for the integration owner with a deep link to the provider within 60 seconds And no more than one notification is sent per 6 hours until resolved And upon receiving reinstall/consent confirmation via webhook or polling, re-registration resumes automatically within 60 seconds And challenge/response or signature validation succeeds and the webhook is marked healthy And the entire flow is captured as a single correlated audit trace
Rollback on Post-Registration Validation Failure
Given a webhook was re-registered When provider challenge/response fails or subsequent signature verification fails within 5 minutes Then the system rolls back to the last known working configuration within 60 seconds And a circuit breaker is opened for 15 minutes for this integration to prevent thrashing And an alert is sent to on-call and the integration owner within 60 seconds with failure details and next steps And an audit log entry records rollback details and the secret_version_id remains unchanged from the previous working state
Jittered Retry & Anti-Thrashing Guardrails
Given re-registration attempts are needed When a previous attempt failed without a hard-stop condition Then retries use exponential backoff with full jitter: base=5s, cap=15m, max 5 attempts per hour per integration And 429/Retry-After and rate limit headers are honored to schedule the next attempt at or after the provider-specified time And only one active re-registration attempt per integration is allowed at a time And the circuit breaker opens after 3 consecutive failures within 10 minutes and transitions to half-open after 15 minutes
Backfill Missed Events Safely
Given a webhook is restored after an outage window When re-registration succeeds and validation passes Then the system queries provider APIs for events between last_successful_delivery_timestamp and now And events are deduplicated by provider event ID or content fingerprint to prevent duplicates in ingestion And 99% of backfillable events up to 10,000 are ingested within 10 minutes of restoration And no more than 0.1% duplicate events are observed downstream And the ranked queue and theme map reflect the backfilled events within 2 minutes of ingestion
Auditable Change Log & Observability
Given any re-registration, secret rotation, rollback, or consent refresh occurs When the action is executed Then an append-only audit record is written within 5 seconds with fields: timestamp, correlation_id, provider, integration_id, action, scopes, secret_version_id (no secret), outcome, actor=system, and checksum And audit records are immutable, retained for at least 365 days, and queryable by correlation_id And each re-registration operation exposes metrics (success/failure, latency, attempts, backfills) and traces linking health check → registration → validation → backfill
Missed Event Detection & Safe Backfill
"As a product manager, I want EchoLens to detect and backfill missed events after an outage so that our insights and prioritization reflect the true customer signal."
Description

Identifies and reconciles gaps by comparing last processed cursors (IDs/timestamps) against provider event logs, fetching and replaying missed events in order within a configurable retention window. Ensures safe backfill via idempotency keys, deduplication, and quarantine for malformed or out-of-scope events. Throttles requests to respect provider quotas and prioritizes backfill without starving real-time ingestion. Updates clustering and scoring incrementally so the live theme map and ranked queue reflect corrected history as backfill progresses.

Acceptance Criteria
Gap Detection via Cursor Comparison After Webhook Revocation
Given a source integration with last_processed_cursor=evt_102 at 10:00Z and provider event log contains evt_103..evt_110 up to 10:30Z When the detector runs after a webhook revocation or on scheduled scan Then it identifies evt_103..evt_110 as missing based on provider ordering and records an audit entry with cursor_before=evt_102, cursor_after=evt_110, missing_count=8 within 60s of scan start And detection is limited to events within configured retention_days and events older than the window are labeled expired_backfill and excluded from fetch
Ordered Backfill Within Retention Window
Given missing events detected across multiple pages When the system fetches backfill Then events are replayed strictly in ascending provider sequence/timestamp with monotonic cursor advancement; no event with later sequence is processed before an earlier one And commit of new cursor occurs only after successful processing of each event; on failure, processing resumes from the last confirmed cursor without skipping
Idempotent Replay and Deduplication
Given the same event is encountered via backfill and real-time or across multiple backfill attempts When processing occurs with idempotency_key = hash(provider_event_id + source) Then the event’s side effects are applied exactly once and duplicates are dropped with dedup_count incremented and no changes to downstream state And re-running the identical backfill window produces identical resulting state and counts
Quarantine Handling for Malformed/Out-of-Scope Events
Given a fetched event has invalid schema or an unsupported type When validation runs Then the event is moved to a quarantine store with reason_code, payload snapshot, and source metadata and is excluded from clustering/scoring and cursor advancement And a quarantine metric and audit log entry are emitted and the item can be retried manually or auto-reprocessed after rule update without being lost
Rate-Limit-Aware Backfill Throttling
Given provider responses include 429 or rate-limit headers When backfill requests are issued Then the system backs off using exponential backoff with jitter and honors Retry-After, ensuring effective request rate stays at or below configured backfill_max_qps And no hard failures are recorded due solely to rate limits; backfill continues and completes once limits allow
Backfill Priority Without Starving Real-Time Ingestion
Given real-time ingestion is active and backfill is running When system load increases Then backfill uses at most the configured share of workers/throughput (backfill_share) and real-time p95 ingestion lag remains below real_time_max_lag_secs; if breached, backfill auto-throttles within 30s until lag recovers And no real-time message is delayed beyond the configured absolute ceiling real_time_max_lag_secs
Incremental Correction of Clustering and Scoring During Backfill
Given backfill events update historical context When batches of up to 500 events are processed Then theme clusters and impact scores are updated incrementally and the live theme map and ranked queue reflect corrections within 60s of batch completion And metrics show no double-counting; previously missing items appear in correct themes; overall totals reconcile to provider counts after backfill completes
Idempotent Ingestion & De‑duplication
"As a platform engineer, I want ingestion to be idempotent so that reprocessing and backfills don’t create duplicate data or distort metrics."
Description

Provides exactly-once semantics across replays and backfills by generating deterministic fingerprints (e.g., provider event ID + source ID + payload hash) and maintaining a processed-ledger with time-bounded windows for out-of-order events. Upserts downstream records instead of duplicating them, ensuring stable theme counts and scores. Propagates idempotency to downstream integrations (e.g., Jira/Linear ticket sync) to prevent duplicate issues or comments during retries and replays.

Acceptance Criteria
Provider Replays After Webhook Re‑registration
Given an event E with fingerprint F already exists in the processed‑ledger When the same event E is received again via provider replay or retry Then no new event record is inserted in the internal store And no downstream side effects (Jira/Linear issue creation or comment posting) are re‑invoked And an idempotency_hit telemetry event is recorded with fingerprint F
Out‑of‑Order Event Arrival Within Dedupe Window
Given two events A and B with timestamps t1 < t2 that fall within the configured dedupe window And B arrives before A When A later arrives Then the unique event count equals 2 regardless of arrival order And theme counts and scores are identical to processing in chronological order And no duplicate downstream actions are produced for either event
Backfill Without Duplicating Downstream Tickets/Comments
Given a backfill job retries N historical events and M of those were previously processed and synced to Jira/Linear using a stable external correlation key When the backfill job runs Then exactly M existing Jira/Linear artifacts are updated idempotently without creating duplicates And only N−M new artifacts are created when creation rules are met And no duplicate comments are added for events that previously posted identical comments And the total count of artifacts after the job equals the number of unique events requiring them
Deterministic Fingerprint Normalization Across Payload Variations
Given semantically identical payloads that differ only by JSON key order, whitespace, or configured non‑semantic fields When fingerprints are generated Then the fingerprints are identical for those payloads And any change to canonical fields produces a different fingerprint And the same input payload always yields the same fingerprint across deployments
Processed‑Ledger Retention and Safe Reprocessing After Expiry
Given processed‑ledger retention is configured to 7 days And an event with fingerprint F was processed 8 days ago When the same event is received again Then it is treated as new for ingestion but downstream writes use upsert semantics to avoid duplicate artifacts And a ledger_miss_due_to_expiry telemetry event is recorded for F And ledger entries older than 7 days are evicted without affecting newer entries
Cross‑Source Non‑Deduplication Guarantee
Given two events share the same provider_event_id and payload hash but originate from different source_id values When processed Then two distinct fingerprints are generated that include source_id And both events are ingested and eligible for downstream actions independently And no duplicate‑suppressed logs are emitted for either event
Idempotent Upsert in Internal Event Store
Given an event with existing fingerprint F is re‑received with differences only in non‑canonical fields When persisted Then exactly one row with key F exists after processing And created_at remains unchanged while updated_at is advanced And derived aggregates recompute once without incrementing counts twice
Adaptive Retry, Backoff & Rate Governance
"As a reliability engineer, I want retries and rate limits tuned per provider so that recovery is fast without risking bans or degraded service."
Description

Implements provider-aware retry policies with exponential backoff, jitter, and circuit breakers to handle transient failures and prevent cascading outages. Enforces per-provider and per-source rate limits, concurrency caps, and burst budgets during both normal ingestion and backfill. Automatically resumes streams after cooldowns and surfaces retry budgets/exhaustion to monitoring for proactive intervention.

Acceptance Criteria
Normal ingestion: 429 rate limit with Retry-After
Given normal ingestion calls a provider that responds with HTTP 429 and a Retry-After header of 5 seconds When retries are scheduled Then the first retry is delayed for at least 5 seconds plus randomized jitter between 10% and 30% And subsequent retries use exponential backoff with factor 2 up to a maximum delay of 60 seconds And total attempts do not exceed the retry budget of 5 And no more than 100 requests per minute are sent to the provider during the retry window
Circuit breaker opens on persistent 5xx
Given a provider returns 5 consecutive HTTP 5xx responses within 60 seconds When issuing requests to that provider Then the circuit breaker transitions to OPEN for 120 seconds And no requests are sent to that provider while OPEN And after 120 seconds the breaker transitions to HALF-OPEN and sends a single probe request And on a 2xx response the breaker transitions to CLOSED; on non-2xx it returns to OPEN for another 120 seconds And breaker state changes and counts are emitted as metrics within 10 seconds
Per-provider and per-source concurrency caps
Given the per-provider concurrency cap is 10 and the per-source cap is 2 When 50 concurrent tasks attempt to call the provider from 5 sources Then in-flight requests never exceed 10 across the provider and 2 per source And excess tasks are queued and executed FIFO And no task is dropped due to caps And queue length, wait time p95, and rejections are recorded as metrics
Backfill isolated rate governance
Given normal ingestion operates at 70 requests per minute and a backfill job starts with a burst budget of 20 requests per minute under a provider global cap of 100 requests per minute When both operate simultaneously Then combined throughput does not exceed 100 requests per minute And backfill throughput does not exceed 20 requests per minute And normal ingestion maintains at least 70 requests per minute And if HTTP 429 is received, backfill pauses and resumes after cooldown with exponential backoff and jitter
Retry budget exhaustion surfaced to monitoring
Given a job has a retry budget of 5 and continues to fail When the final attempt fails or a circuit breaker remains OPEN for more than 10 minutes Then a retry_budget_exhausted event is emitted within 30 seconds including provider_id, source_id, job_id, attempt_count, and last_error_code And a warning alert is sent to monitoring with a link to the affected stream And the job transitions to a terminal state requiring manual or scheduled requeue
Automatic resume after cooldown
Given a stream is paused due to hitting the provider rate limit and the provider reset window elapses When the cooldown expires Then the stream resumes automatically within 15 seconds And initial throughput is limited to 50% of configured caps for 60 seconds before ramping to full caps And no burst exceeds the configured burst budget during ramp-up
Jitter prevents thundering herd
Given 100 parallel requests experience the same transient HTTP 503 error When exponential backoff with jitter is applied Then the first retry timestamps across requests have a 95th percentile spread of at least 500 milliseconds And jitter is uniformly distributed within ±20% of the calculated backoff And jitter behavior is deterministic in test mode via a seeded RNG
Monitoring, Alerts & Audit Trail
"As a CX lead, I want clear visibility and alerts on webhook health and recovery actions so that I can trust the accuracy of insights and act quickly when needed."
Description

Delivers a real-time dashboard of webhook health, last healthy timestamps, drift reasons, gap sizes, backfill progress, and MTTD/MTTR. Emits alerts to Slack/Email/PagerDuty on health changes, repeated retries, and quota pressure. Captures a tamper-evident audit log for registrations, secret rotations, backfill actions, and operator overrides, exportable for compliance. Integrates with product-level metrics so PMs can correlate auto-heal events with theme-map shifts and scoring changes.

Acceptance Criteria
Real-Time Webhook Health Dashboard Update
Given a webhook transitions state (healthy, degraded, failed) When the system ingests the state-change event Then the dashboard reflects the new state within 10 seconds and without manual refresh Given a webhook with at least one successful delivery When viewing its details Then the Last Healthy timestamp is shown in ISO 8601 UTC and equals the most recent successful delivery timestamp; if none exists, the field displays "—" Given MTTD and MTTR must be reported When viewing the dashboard for trailing 24h and 7d windows Then both metrics match a reference calculation within ±5% for the same dataset Given a tenant with up to 10,000 webhooks When filtering by source system and environment Then results render within 2 seconds at the 95th percentile
Drift Detection and Reason Visibility
Given drift types (revoked, expired, secret_mismatch, endpoint_changed, scope_changed) When drift is detected for a webhook Then the dashboard shows the primary reason code and a human-readable summary with source reference ID Given multiple drift signals for the same webhook When reasons are ranked Then priority order is revoked > expired > secret_mismatch > scope_changed > endpoint_changed and is displayed deterministically Given an operator marks a detection as false positive When the override is saved Then the reason updates to "operator_override:false_positive" and an audit entry is created with actor, timestamp, and prior reason
Gap Size Estimation and Backfill Progress Tracking
Given missed events are identified for a webhook When viewing the gap panel Then gap size is shown as event count and duration, where count equals backfill queue size and duration = now - earliest missing event created_at Given an active backfill job When progress updates occur Then the UI shows processed/total, percent complete, and ETA updated at least every 15 seconds Given a backfill completes When the job status is terminal Then the backfill status shows Completed and residual gap size = 0; if not all items succeeded, status shows Partial with residual > 0 and a retry action is available
Alerting on Health Changes and Retry Storms
Given alert channels (Slack, Email, PagerDuty) are configured When a webhook transitions healthy→degraded, degraded→failed, or failed→healthy Then an alert is sent to all configured channels within 60 seconds containing integration_id, current_state, reason, last_healthy_at, gap_size, and incident URL Given retry storm policy (default threshold 50 retries/5 minutes per webhook) When the threshold is exceeded Then a Warning alert is issued and deduplicated for 10 minutes; if the condition persists ≥15 minutes, a Critical alert is sent to PagerDuty Given tenant quiet hours and severity routing rules When alerts are generated Then delivery follows those rules (e.g., Email only during quiet hours, PagerDuty for Critical outside quiet hours)
Quota Pressure Alerting and Visibility
Given provider quota metrics are available When utilization reaches ≥80% of the current window Then a Quota Pressure warning is displayed on the dashboard and sent to Slack/Email; at ≥95% a Critical alert is sent to PagerDuty Given quota utilization and reset time from the provider When the dashboard renders Then utilization percentage and window reset time match provider values within ±2% Given repeated quota crossings When multiple alerts would fire within 10 minutes for the same integration and severity Then alerts are deduplicated and a single thread/update is posted instead
Tamper-Evident Audit Log and Export
Given auditable actions (registration, secret_rotation, backfill_start, backfill_complete, operator_override) When any action occurs Then an audit entry is appended with timestamp, actor, action_type, target_id, result, and SHA-256 payload hash Given the audit log uses hash chaining When any stored entry is modified or removed Then chain verification fails and an Integrity Failed banner appears within 60 seconds with a pointer to the first broken link Given a user requests an export for a date range up to 1,000,000 rows When the export is initiated Then CSV and JSONL files are generated within 5 minutes and a signed URL valid for 24 hours is provided; the export content exactly matches filtered audit entries Given audit log filters (entity, actor, action_type, status) When a query is executed on a dataset of 1,000,000 rows Then the 95th percentile response time is ≤5 seconds
Correlation of Auto-Heal Incidents with Theme Map and Scoring
Given auto-heal incidents and product-level metrics are available When a time window is selected Then the UI overlays incident markers on theme-map and scoring charts and computes correlations; for any metric with |rho| ≥ 0.5 and p < 0.05, a correlation hint is displayed with metric name and window Given a user clicks an incident marker When the detail view opens Then charts are focused to ±24 hours around the incident and show deltas for the top 5 affected themes by absolute change Given streaming updates When new incidents or metric updates occur Then the correlation view refreshes within 10 seconds; if no significant correlations exist, the view displays "No significant correlation" with the last evaluated timestamp
Configuration UI, RBAC & Safe Modes
"As an administrator, I want configurable controls and permissions for auto-heal so that we can tailor behavior per source and environment without risking production data."
Description

Provides UI and API controls to enable/disable auto-heal per source, configure retention windows, retry budgets, and alert channels, and select which event types to subscribe. Enforces role-based access for sensitive actions (e.g., secret rotation, forced backfill) and supports environment scoping (dev/stage/prod). Includes dry-run and sandbox test modes to validate registrations and handshakes without impacting production ingestion.

Acceptance Criteria
Toggle Auto-Heal Per Source (UI & API)
Given a connected source in prod, When an Admin disables Auto-Heal via UI and confirms, Then the source’s autoHeal flag persists to false within 2 seconds and reflects on refresh. And an audit log entry is recorded with actor, timestamp (UTC), environment, sourceId, oldValue=true, newValue=false. And no ReRegisterAttempt events are emitted for that source in the next 10 minutes while disabled. Given PATCH /v1/sources/{id}/settings with body {"autoHeal":true}, When called by a Maintainer or Admin with a valid token, Then respond 200 and persist change; When called by a Viewer or invalid token, Then respond 403 forbidden. When enabling auto-heal on a source missing required secrets, Then respond 422 missing_secret and do not change current state.
Configure Retention Window & Retry Budget by Source and Environment
Given stage environment, When Admin sets retentionWindowDays=30 and retryBudget=20, Then inputs are validated within ranges (retentionWindowDays:1–90, retryBudget:0–100); out-of-range returns 400 with field-level errors. When saved, Then effective values are visible via GET /v1/sources/{id}/settings and in UI within 2 seconds. And the scheduler uses the new values for that environment within 5 minutes (metrics show settings_applied=true with matching values). Attempts by Viewer to modify return 403; all changes emit audit log entries with before/after diffs. When retryBudget=0, Then auto-heal performs no retries and logs policy_blocked=true on would-be retry events.
Configure Alert Channels (Email, Slack, PagerDuty)
Given a source, When an Admin configures alert channels with Slack webhook and Email, Then both are validated (webhook test returns HTTP 2xx; email format is RFC-compliant) and saved. When "Send Test Alert" is clicked, Then each channel returns a delivery_status within 10 seconds; success shows delivery_status=success, failure shows delivery_status=failure with reason. Secrets are stored encrypted-at-rest; UI redacts values; GET APIs return last4 only. Secret rotation preserves previous credentials for up to 15 minutes for seamless cutover; during overlap both versions are accepted; action is audit-logged. If the primary channel fails 3 consecutive deliveries within 15 minutes, Then system falls back to the next configured channel and emits alert_channel_degraded.
Select Event Types to Subscribe
Given the Events selector, When a Maintainer selects supported event types and clicks Save, Then validation passes against provider schema; unsupported types return 422 invalid_event_type listing names. On successful save in dev, Then webhook registration is updated and handshake returns 2xx; if provider returns non-2xx, Then previous subscription is restored (rollback) and an error with correlationId is shown. Dry-run preview shows added/removed event types and estimated 30-day volume impact (with 95% CI); no provider changes occur in dry-run. Changing subscriptions in one environment does not affect others; GET settings for prod remain unchanged after a dev change. All changes generate an audit entry and increment configuration_version.
RBAC for Secret Rotation and Forced Backfill
Roles are defined as Admin, Maintainer, Viewer; GET /v1/roles returns these constants. Secret rotation and forced backfill require Admin; Maintainer attempting either receives 403 forbidden. Admin must complete MFA within 5 minutes to perform these sensitive actions; missing/expired MFA yields 401 mfa_required. All sensitive actions write immutable audit records with eventId, actorId, role, ip, environment, resourceId, and before/after hashes. Viewer role can access read-only endpoints and UI; any POST/PATCH/DELETE attempt returns 403.
Environment Scoping (Dev/Stage/Prod)
Each environment maintains distinct webhook endpoints, secrets, and settings; UI displays an environment badge; API responses include X-EchoLens-Env. If a user attempts to reference a secret or endpoint from another environment, Then respond 422 cross_env_reference with details and do not save. Role bindings are environment-scoped; being Admin in dev but Viewer in prod restricts prod edits (403 on write attempts). Export/Import includes environment markers; importing a prod export into dev requires explicit remapping; proceeding without remapping is blocked. Deleting a source in one environment does not affect other environments; verified by GET in each environment post-delete.
Dry-Run and Sandbox Test Modes
When Dry-Run mode is enabled for a source, Then registration and handshake calls execute in simulation using test headers (X-EchoLens-Test:true) and/or sandbox endpoints; no production subscription is created. Dry-Run produces a report including DNS reachability, TLS validity, auth scopes, event type diffs, and retry/retention impacts; the report is viewable in UI and via GET /v1/dry-runs/{id} for 7 days. Sandbox Test emits synthetic events for selected types and verifies receipt to internal ingress without persisting to production analytics; run completes within 2 minutes with pass/fail per event type. Exiting Dry-Run or Sandbox restores normal scheduler state; no ingestion-loss occurs; an audit record is written. In prod, starting Sandbox requires Admin and explicit confirmation; attempts by Maintainer/Viewer return 403.

Drift Watch

Monitors schema changes like renamed fields, new custom attributes, or deleted tags and auto‑updates mappings with a clear diff. Avoids broken pipelines and preserves historical continuity without manual rework.

Requirements

Real-time Schema Drift Detection
"As a platform admin, I want EchoLens to automatically detect schema changes across all sources so that I can prevent ingestion breakages and keep analytics and theme maps accurate without manual monitoring."
Description

Continuously monitor connected data sources (e.g., Zendesk, Intercom, email parsers, app event streams) for schema changes including renamed fields, new custom attributes, and deleted tags. Maintain time-stamped schema snapshots and run diffing to detect drift with confidence scores, covering typed and untyped payloads. Expose detection via internal events and webhooks, and surface a concise summary of impacted mappings, pipelines, and theme clusters so triage is proactive rather than reactive. Ensures EchoLens ingestion and clustering remain stable and avoids broken pipelines that would degrade the ranked queue and live theme map.

Acceptance Criteria
Renamed Field Detection and Mapping Auto-Update
Given EchoLens has a baseline schema snapshot for a connected source at time T0 And incoming records after T0 contain a new field name F' whose values match historical values of deprecated field F with ≥90% similarity over the last 1,000 records And the source's schema metadata indicates F is no longer present When the system processes the next batch/stream segment Then the drift engine classifies this as a field rename F -> F' with confidence ≥0.85 And a time-stamped diff is created showing rename F -> F' And existing mappings that referenced F are automatically re-bound to F' without manual intervention And no ingestion errors are raised for pipelines using F (0 broken pipelines) And an internal drift-detected event is emitted within 10 seconds and a webhook is delivered within 60 seconds And the detection is idempotent: repeated evidence within 24 hours does not create duplicate rename records
New Custom Attribute Detection with Type Inference
Given a connected source starts emitting a previously unseen attribute A in payloads And A appears in ≥50 records or is declared in the source's schema endpoint When the drift engine evaluates schemas within a 5-minute window Then A is classified as "added attribute" with an inferred primitive type (string/number/boolean/timestamp) and nullability And a time-stamped diff is created marking A as added And confidence score for the addition is ≥0.9 when declared by source schema, or ≥0.8 when inferred from payloads And suggested mapping targets are generated where applicable And internal event and webhook are emitted with attribute name, type, confidence, and impacted pipelines And no existing pipelines fail due to the presence of A
Deleted Tag/Field Detection with Historical Continuity
Given field/tag D existed in baseline snapshot T0 and has zero occurrences in live payloads for 48 consecutive hours And the source schema no longer advertises D When the drift engine runs scheduled verification Then D is classified as "deleted" with confidence ≥0.9 And a time-stamped diff marks D as removed And historical data and mappings referencing D remain queryable without loss (no backfill/drop) And active pipelines that previously used D continue running with graceful degradation and a deprecation notice And internal event and webhook are emitted including first-seen and last-seen timestamps for D
Time-Stamped Schema Snapshots and Diff Retention
Given EchoLens maintains per-source schema versions When a drift event is detected or 24 hours elapse without drift Then a new schema snapshot is persisted with an ISO-8601 timestamp and monotonic version number And diffs between consecutive versions enumerate adds, renames, deletes with affected fields and types And snapshots and diffs are retrievable via API with pagination And at least 180 days of versions are retained by default, or up to configured retention And concurrent snapshot writes are serialized to prevent version gaps And system restarts do not lose committed versions (durable storage)
Confidence Scoring Across Typed and Untyped Payloads
Given sources may emit typed schemas (e.g., Zendesk) or untyped JSON (e.g., email parser) When drift candidates are evaluated Then confidence scores in [0,1] are computed using type metadata when available and value-distribution heuristics when not And events, webhooks, and UI/API surfaces include the confidence score and rationale codes And rename detections below 0.7 confidence are flagged as "needs review" and do not auto-update mappings And all accepted auto-actions require confidence ≥0.8
Internal Events and Webhook Delivery for Drift
Given a drift (add/rename/delete) is confirmed When publishing notifications Then an internal event is emitted within 10 seconds of confirmation And a webhook POST is sent within 60 seconds with HMAC-SHA256 signature, idempotency key, event type, source id, version, diff, and confidence And failed webhook deliveries are retried at least 6 times with exponential backoff up to 30 minutes And duplicate webhook deliveries for the same idempotency key are not processed twice by the receiver And delivery metrics (success, latency, retries) are recorded and queryable
Impact Summary of Mappings, Pipelines, and Theme Clusters
Given a drift is detected When the impact analyzer runs Then a concise summary is generated listing impacted mappings, active pipelines, and theme clusters with counts and severity And the summary is available via API and updates the triage queue within 2 minutes And each listed impact includes a remediation suggestion or auto-fix status And no impacted item is missed with recall ≥0.95 on a seeded test dataset And the ranked queue and live theme map remain operational without degradation (no increase in ingestion error rate)
Auto-Mapping Update Engine
"As a data engineer, I want mappings to auto-update when fields are renamed or added so that ingestion and downstream analytics continue working without manual remapping."
Description

Automatically update internal field mappings when drift is confirmed, creating alias relationships for renamed fields and safely incorporating new attributes into the canonical EchoLens model. Apply deterministic rename heuristics and similarity matching with confidence gating, simulate the change, and then roll out updates to ingestion transforms with zero/low downtime. Update downstream processors (clustering, confidence scoring, ranking) to ensure continuity without manual rework and preserve compatibility for Jira/Linear sync.

Acceptance Criteria
Renamed Field Alias Creation After Confirmed Drift
Given a source field is renamed and drift is confirmed with confidence >= 0.90 When the engine processes the change Then it creates an alias mapping from old_name to new_name and versions the mapping incrementally Given the alias mapping exists When new events arrive using either old_name or new_name Then both resolve to the same canonical field with no duplicate values produced Given the alias mapping exists When historical records referencing old_name are reprocessed Then values resolve to the canonical field with zero transform errors and no WARN/ERROR logs related to schema resolution Given the change is applied When a diff is generated for the release Then the diff includes before/after field names, confidence score, heuristic used, and timestamp for the alias creation
New Custom Attribute Addition to Canonical Model
Given a previously unseen attribute appears in the source When similarity to existing canonical fields is < 0.70 and whitelist/allow rules permit Then a new canonical field is created with inferred type and default null-handling policy, and the mapping is versioned Given the new canonical field is created When validated on a 10,000-event sample per connector Then transform error rate is <= 0.10% and field type inference accuracy is >= 95% Given the new canonical field exists When downstream processors run on the next scheduled cycle Then there are no job failures attributable to the new field and P95 pipeline latency impact is < 5% during the rollout window
Deleted Field Safe Deprecation
Given a mapped source field is absent for a continuous 24-hour observation window When drift is confirmed for deletion Then the field mapping is marked deprecated, ingestion skips the missing field without exceptions, and a single deprecation alert is emitted per connector Given the field is deprecated When historical data is queried or reprocessed Then previously ingested values remain accessible via the canonical field and any alias chain without data loss Given dependent derived features reference the deprecated field When processors execute Then configured fallbacks are applied and error rate does not exceed baseline by more than 1% over a 24-hour window
Pre-Rollout Simulation and Diff
Given a set of mapping changes is proposed by the engine When simulate is executed Then a diff report is generated listing added/removed/aliased fields, type changes, affected connectors, and confidence per change, and it is stored in the audit log Given simulation output is available When applied to a 10,000-event sample per connector Then projected transform error rate is <= 0.10% and record coverage is >= 99.90%; otherwise rollout is blocked Given auto-approval rules are enabled When all changes meet or exceed confidence and quality thresholds Then rollout is auto-approved; otherwise changes remain pending for review
Zero/Low-Downtime Transform Rollout
Given rollout is approved When ingestion transforms are updated Then updates occur via rolling deployment per connector with cumulative unavailability <= 30 seconds and zero message loss verified by offset continuity Given rollout is in progress When monitoring ingestion lag Then end-to-end lag remains within 2x baseline and returns to baseline within 30 minutes post-rollout for tenants up to P95 volume
Downstream Continuity and Sync Compatibility
Given mapping changes are live When clustering, confidence scoring, and ranking run on the next cycle Then job failure rate remains <= baseline + 0.5% and cluster count/top-10 rank stability stay within ±5% on a 24-hour A/B window Given a Jira or Linear issue is created from a theme referencing a renamed field When sync executes Then payloads use current canonical names, preserve display labels via alias resolution, and complete with 0 sync errors over a test batch of 100 issues Given historical issues created before the rename exist When they are updated after the rename Then field values resolve via alias without 4xx/5xx webhook responses and without duplicate custom fields being created
Diff View and Audit Trail
"As a product manager, I want a human-readable diff and impact summary so that I can understand and trust the changes Drift Watch is making and quickly approve or follow up when needed."
Description

Provide a clear UI diff showing before/after schemas and mapping changes at the field level, including detected renames, additions, deletions, and inferred aliases. Display impact analysis (affected sources, records, clusters, dashboards, and sync targets) and confidence scores per change. Record a permanent, exportable audit trail with actor, timestamp, decision (auto-applied vs. approved), and rollback pointers to support compliance and team accountability.

Acceptance Criteria
Before/After Field-Level Diff Rendering
Given a detected schema change with available previous and current schemas When a user opens the Drift Watch Diff View for that change Then the UI displays a side-by-side before/after view at field level with unchanged fields hidden by default And Added fields are labeled "Added" and highlighted in green; Deleted fields are labeled "Deleted" and highlighted in red And Renamed fields show a single row with old->new names and are labeled "Renamed" And Inferred aliases are labeled "Alias (inferred)" with the alias target shown And each changed field row shows field name, data type, mapping target, and source system for both before and after And The diff renders in under 2 seconds for schemas up to 500 fields And a toggle "Show unchanged" reveals all fields and a counter shows the exact number of changed fields
Change Type Detection and Mapping Update
Given a schema update that includes an added field, a deleted field, and a renamed field When the system processes the change Then the added field appears in the diff with an auto-generated mapping preview to the destination schema And the deleted field appears in the diff with its mapping marked as deprecated and no longer active And the renamed field appears as a single change item with a link between old and new names And inferred alias suggestions are displayed with rationale and can be approved or rejected And upon approval (or auto-apply per configuration), the active mappings are updated accordingly and versioned And all mapping updates are captured as a single change set ID
Impact Analysis Panel and Confidence Scores
Given a processed change set with computed impacts and per-change confidence scores When a user opens the Impact Analysis panel Then totals for affected sources, records, clusters, dashboards, and sync targets are displayed with numeric counts And each affected entity is listed with a clickable link that opens a filtered view of that entity And record impact displays an exact number or an "Estimated" label if sampling was used And every change item shows a confidence score between 0.00 and 1.00 with two decimal places And the list can be sorted by confidence and filtered by a configurable threshold And changes at or above the configured auto-apply threshold are marked "Auto-applied"; those below are marked "Pending approval"
Immutable Audit Trail Entry Creation
Given any mapping change decision (auto-applied, approved, rejected) When the decision is finalized Then an audit entry is written that includes actor (user ID or service account), ISO 8601 UTC timestamp, decision, change set ID, per-change details, and rollback pointer (previous snapshot ID) And a cryptographic hash of the before/after snapshot is stored with the entry And audit entries cannot be edited or deleted via UI or API; any attempt is denied and logged as a separate event And audit entries are retained without automatic expiration And audit entries are searchable by date range, actor, change type, source, and decision And viewing an audit entry reproduces the exact diff snapshot associated with that entry
Audit Trail Export (CSV and JSON)
Given an auditor selects a date range and optional filters in the Audit Trail When the auditor requests an export in CSV or JSON Then the export includes: actor, timestamp (UTC), decision, change set ID, per-change details (field name, type, change type), confidence score, rollback pointer, and impact summary And the exported file contains a header/schema description and a SHA-256 checksum for integrity verification And exports up to 100,000 entries complete within 30 seconds and are downloadable as a single file per format And export content matches on-screen filtered results 1:1 And a corresponding audit entry records the export action with actor, timestamp, and filter parameters
Rollback via Audit Trail Pointer
Given a change set with a valid rollback pointer to a prior mapping snapshot When an authorized user initiates rollback and confirms the action Then the system reverts the active mappings to the referenced snapshot successfully And a new audit entry is recorded with decision "Rolled back" linking both the original and rollback change set IDs And the Diff View updates to show the rollback as a new change with recalculated impact analysis And if a conflict prevents rollback (e.g., destination field no longer exists), the operation is blocked with a clear error and no partial changes are applied And rollback is idempotent; repeating the rollback does not change state further And downstream sync targets are updated and pipelines are revalidated within 5 minutes
Historical Continuity Backfill
"As a CX lead, I want historical data to remain consistent through schema changes so that my trend analysis and prioritization aren’t disrupted by discontinuities."
Description

When mappings change (e.g., a field is renamed or a tag is deleted), perform non-destructive, idempotent backfills to reindex historical records using the alias map so trend lines, clusters, and the live theme map remain continuous. Schedule backfill windows to respect API rate limits and source quotas, track progress with checkpoints, and verify that derived metrics and confidence scores remain stable post-backfill.

Acceptance Criteria
Field Rename Backfill Preserves Trend Lines
Given a source field named "old_name" is renamed to "new_name" and the alias map is updated When a historical backfill is triggered for the affected indices covering the last 24 months Then 100% of records previously referencing "old_name" are reindexed to "new_name" with raw originals retained unchanged And indices are written to a new version and swapped atomically via alias with zero downtime And daily and weekly aggregates for affected metrics differ by <= 1% relative or <= 5 absolute per bucket compared to pre-rename baselines And cluster membership over the top 100 themes has Jaccard similarity >= 0.98 and the live theme map contains no broken references
Backfill Scheduling Honors API Rate Limits and Quotas
Given per-source limits are configured (rate limit 600 req/min, daily record quota 500,000, max_parallel_windows_per_source=2) When the backfill plan is generated and executed Then observed requests per source over any 60s sliding window <= 90% of the configured rate limit And total records processed per source per day <= 95% of the configured quota And no requests fail with HTTP 429 due to our issuance; on any 429/503 received, exponential backoff with jitter is applied with <= 5 retries per request And no more than 2 parallel windows run per source concurrently And the schedule log includes planned windows, expected duration, and applied limits
Checkpointed Resume Without Duplication
Given a backfill run is interrupted after partial completion When the run is resumed Then processing restarts from the last durable checkpoint and completes with missed records = 0 and duplicates = 0 And checkpoint granularity is <= 10,000 records or 5 minutes, whichever comes first And idempotency keys ensure exactly-once writes across retries and resumes (verified by zero net diffs on a second resume) And the public status API reports run_id, phase, percent_complete, ETA, and last_checkpoint_time
Alias Map Diff and Non‑Destructive Mapping Update
Given schema changes include renames, new attributes, and deleted tags When an alias map update is proposed Then a human-readable diff lists added/removed/renamed entries with impacted-record counts per change and a machine-readable JSON diff is stored And applying the update writes to a new index/version and performs an atomic alias swap; the previous index is retained for >= 7 days and can be rolled back in one click And deletions of tags must map to an explicit target per policy; unmapped deletions fail validation and block execution And audit metadata (actor, timestamp, checksum of mapping) is persisted and queryable
Idempotent Backfill Re-run Produces No Net Changes
Given a backfill has completed successfully for a given mapping version and time range When the exact same backfill is re-run with identical parameters Then zero records are modified/inserted/deleted (write count = 0) and affected index version checksums remain unchanged And derived aggregations and theme rankings have Kendall tau >= 0.99 and confidence score MAE <= 0.005 vs. the immediately prior state And the re-run exits early with a "no-op" status in <= 2 minutes and emits a no-op summary
Post-Backfill Metric and Confidence Score Stability
Given pre-backfill baselines for trend lines, cluster assignments, theme rankings, and confidence scores are captured When the backfill completes Then validation reports: trend line MAE <= 1% on affected metrics; >= 99% of items retain their cluster assignment; Kendall tau between pre/post theme ranks >= 0.97; confidence score MAE <= 0.01 with mean delta within ±0.002 And if any threshold is breached, the alias swap is automatically rolled back and the run is marked Failed with detailed diagnostics
Safe Rollback and Versioning
"As a platform owner, I want versioned configs and safe rollback so that I can quickly revert problematic mapping updates without losing data or disrupting teams."
Description

Version schemas, mappings, and transformation configs with immutable snapshots and one-click rollback at source or workspace scope. Support dry-run replays against prior versions, compare metrics pre/post, and provide automatic guardrails to prevent data loss. Enable pausing of auto-apply behavior and staged rollouts (canary, per-source) to reduce risk during high-impact changes.

Acceptance Criteria
Immutable Snapshot Creation and Auditability
Given a schema, mapping, or transformation change is detected for a source When the change is applied (auto or manual) or proposed Then an immutable snapshot version is created capturing schema, mappings, transformations, and a structured diff And the snapshot records metadata: version ID, actor, timestamp (UTC), scope (source/workspace), and change summary And any attempt to modify a created snapshot is rejected and logged And the snapshot is retrievable via UI and API within 3 seconds of creation And at least the last 100 versions per source are retained and listable
One‑Click Source Rollback without Data Loss
Given source X is on version vN and a previous version vN-1 exists When a user initiates a one‑click rollback to vN-1 at source scope and confirms Then the active configuration for source X switches to vN-1 without mutating historical data And ingest for source X is paused for no more than 30 seconds during the switch And mappings and transformations are re-bound to vN-1 with no manual edits required And post‑rollback error rate remains ≤0.1% over the next 10,000 processed events And an audit log entry is created with user, time, from→to versions, and reason And configured notifications (e.g., Slack/Webhook) are sent within 60 seconds
Atomic Workspace‑Level Rollback to Labeled Release
Given a labeled release R1 references consistent versions for all workspace sources When a user rolls back the entire workspace to R1 Then either all targeted sources switch to R1 atomically or none do if any pre‑check fails And pre‑checks validate compatibility, dependency constraints, and guardrails before changes apply And total ingest downtime per source during the rollback is ≤60 seconds And the system provides a single‑click roll‑forward to the pre‑rollback state And a consolidated report lists succeeded/blocked sources and reasons
Dry‑Run Replay with Pre/Post Metrics Comparison
Given a prior version vTarget is selected for source X with a 24‑hour lookback window When a dry‑run replay is executed Then no production data or configurations are mutated And the system processes either the full 24‑hour window or at least 100,000 events, whichever is smaller And it outputs pre/post metrics: ingest success rate, transformed record count, error rate, dropped/added/renamed field counts, and p95 processing latency And metric deltas are displayed in UI and exportable as CSV/JSON And the dry‑run completes within 10 minutes for the evaluated sample And the user can promote vTarget to a staged rollout directly from the results
Automatic Guardrails to Prevent Data Loss
Given a rollout or rollback may drop, rename, or retype fields with existing historical data When guardrail pre‑checks run Then destructive changes are blocked by default if projected data loss > 0 rows And safe fallbacks are applied where possible (e.g., mapping renames, default values, or field deprecation flags) And an explicit typed confirmation with reason is required to override any block, creating a backup snapshot and export manifest And rollback never performs record‑level deletions and preserves historical lineage And the UI highlights all at‑risk fields with impact estimates before execution
Pause Auto‑Apply at Source or Workspace Scope
Given auto‑apply for drift fixes is enabled When a user pauses auto‑apply at source or workspace scope with an optional expiry Then no new detected drifts are auto‑applied while paused; they are queued for review And a visible banner shows paused scope, actor, timestamp, and expiry in UI and API And scheduled jobs respect the paused state; manual applies remain available with confirmation And resuming auto‑apply processes the queued changes only after user review/approval
Staged Rollouts with Canary and Auto‑Rollback
Given a new version vN is ready for deployment to a source group When a staged rollout is configured (e.g., 10% → 50% → 100%) with an error‑rate threshold of 2% over 5 consecutive minutes Then the canary stage routes only the configured subset/percentage of sources to vN And progression to the next stage occurs automatically only if all guardrail metrics remain within thresholds And if thresholds are breached at any stage, the system auto‑rolls back affected sources to the prior version and sends notifications And per‑source inclusion/exclusion is supported, with real‑time dashboards of rollout status and metrics And the final stage marks vN as stable and updates the release label
Notifications and Approval Workflow
"As a team lead, I want drift alerts with one-click approval options in Slack so that I can review and act quickly without logging into the console."
Description

Send targeted alerts to Slack/Email with a concise diff, confidence scores, and estimated impact. Allow policy-driven actions: auto-apply below a risk threshold, require approval above it, or route to owners by source/integration. Support inline approval from Slack, comment threads, and escalation rules to ensure timely responses without leaving the team’s workflow.

Acceptance Criteria
Slack Inline Approval for High-Risk Schema Change
Given a schema change is detected with risk score above the policy threshold for its integration When Drift Watch posts an alert to the designated Slack channel Then the message includes a concise diff (max 5 lines), risk score (0–100), confidence score (0–100), estimated impact (# records affected in last 24h and # dependent pipelines), Approve and Reject buttons, and a link to the full diff Given an authorized approver clicks Approve in Slack When the action is received by EchoLens Then the mapping update is applied within 60 seconds, the change status becomes Applied, and an audit record (actor, timestamp, diff hash, previous/new mapping) is stored Given an authorized approver clicks Reject in Slack When the action is received by EchoLens Then no mapping is changed, the change status becomes Rejected, and if task sync is enabled a Jira/Linear ticket is created with the diff Given a non-authorized user clicks an action button When the action is processed Then the action is denied and an ephemeral Slack message explains required permissions
Auto-Apply Low-Risk Mapping Updates
Given a workspace policy with an auto-apply threshold is enabled When a schema change is detected with risk score less than or equal to the threshold Then the mapping is updated automatically within 60 seconds without manual approval And a Slack/Email notification is sent indicating Auto-applied with the diff, risk score, confidence score, and estimated impact Given an auto-apply attempt fails When the update cannot be completed Then the system rolls back to the previous mapping, sets status to Failed, and sends an alert with error details and remediation link Given an auto-applied change is later reverted by a user When the revert is confirmed Then the audit log records both actions and the current mapping reflects the revert
Targeted Email Alert with Approve/Reject Deep Links
Given an integration owner and backup are configured for a source When a schema change is detected Then an email is sent to the owner (CC backup) within 2 minutes containing the integration/source, concise diff (max 10 lines), risk score, confidence score, estimated impact, and Approve/Reject deep links Given the recipient clicks Approve from the email When the link is opened and authenticated Then the mapping is applied within 60 seconds and the email thread receives a confirmation notification Given multiple changes are grouped within 10 minutes When emails are generated Then duplicate alerts are suppressed and a single digest is sent with per-change actions
Routing and Escalation by Source/Integration
Given a routing policy defines primary owners and an escalation chain with an SLA of 2 hours When a high-risk schema change is detected Then the initial alert is routed to the primary owner(s) via Slack mention and/or Email as configured Given no approve/reject action is taken within 2 hours When the SLA expires Then the alert is escalated to the next level with context and prior attempts, and this repeats up to the configured maximum levels or until resolved Then all routing and escalation events are logged with timestamps, recipients, and outcomes
Confidence and Impact Metrics in Notifications
Given a schema change notification is generated When the alert is delivered via Slack or Email Then it displays risk score (0–100), confidence score (0–100 with 1-decimal), affected fields count, percent of events impacted over the last 24h, and number of dependent pipelines/jobs Given impact metrics are unavailable for the integration When the alert is generated Then the impact fields show Unknown and auto-apply is disabled, requiring explicit approval
Two-Way Comment Threads and Audit Trail
Given a Slack alert for a schema change has been posted When users add replies in the Slack thread Then the replies (author, text, timestamp, attachments) are captured to the EchoLens change record within 10 seconds Given comments are added in the EchoLens UI on the same change When synchronization runs Then the comments are posted back to the Slack thread without duplication Then the final approve/reject decision is posted to the thread with status, actor, and diff hash, and a complete audit trail is available in EchoLens
Safeguard During Pending Approval to Prevent Breakage
Given a schema change exceeds the approval threshold and is pending decision When ingestion continues during the pending state Then Drift Watch applies a backward-compatible shim to map old and new fields, keeping ingestion error rate below 0.1% over 1 hour and preventing pipeline failures Given the change is approved When normalization is executed Then the shim is removed and the updated mapping is activated within 5 minutes, preserving historical continuity via versioned schema Given the change is rejected When rollback is executed Then the shim is removed, the original mapping remains active, and impacted metrics are recalculated
Preflight Validation and Health Checks
"As a reliability engineer, I want preflight checks that block risky changes so that we maintain pipeline health and avoid regressions in analytics and syncs."
Description

Before applying mapping updates, run preflight validations: sample ingestion runs, type/format conflict checks, downstream contract tests (clustering, ranking queue, Jira/Linear sync), and performance impact estimates. Produce a health score and block auto-apply when critical checks fail, providing actionable remediation steps to keep EchoLens stable and reliable.

Acceptance Criteria
Preflight Sample Ingestion Validation
Given a pending schema mapping update is detected by Drift Watch And a sample set of min(1000, 5% of last 24h events) but at least 100 per source is selected When the preflight job ingests the sample using the updated mapping Then the ingestion completes with overall error rate <= 0.5% And 100% of required fields are mapped with no unresolved fields And any unmapped optional fields are listed with counts in the preflight report And the preflight run ID and metrics are persisted and viewable in the UI
Type and Format Conflict Detection
Given field type or format changes exist between current and proposed schemas When the preflight validator runs type/format checks Then all conflicts are listed with old_type, new_type, affected_sources, and sample_values And known safe coercions (e.g., string->number parse, ISO8601->datetime) are auto-suggested and applied in simulation And any required-field conflict without a safe coercion is marked Critical And if any Critical conflict exists, the health score deducts at least 40 points and the change is flagged Blocker
Downstream Contract Tests: Clustering, Ranking Queue, and Jira/Linear Sync
Given the sample ingestion preflight succeeds When downstream contract tests execute Then the clustering job completes with 0 runtime exceptions and runtime change <= +15% versus baseline And the number of clusters varies by no more than +/-20% versus baseline on the same sample And the ranked queue is produced with a non-empty top 50 and generation time change <= +10% And Jira and Linear test syncs return HTTP 2xx and required destination fields are populated without coercion errors And any failed downstream contract test is classified Critical and blocks auto-apply
Performance Impact Estimation and Guardrails
Given proposed mapping changes may alter field breadth or data volume When performance estimators run on the sample and extrapolate to 24h volume Then predicted p95 ingestion latency increase is <= 10% And predicted clustering CPU-hours increase is <= 15% And predicted storage growth is <= 5% And any metric exceeding its threshold is flagged Warning; if exceeding by more than 2x the threshold, it is classified Critical
Composite Health Score and Auto-Apply Gating
Given all preflight checks complete When the composite health score is computed Then the health score is a 0–100 value with component breakdowns for Ingestion, Conflicts, Downstream, and Performance And the score, failed checks, and suggested remediations are included in a downloadable report And auto-apply is permitted only if score >= 80 and there are no Critical findings And if score < 80 or any Critical finding exists, auto-apply is blocked and the UI displays Blocked with enumerated reasons
Preflight Report Diff and Remediation Actions
Given mapping diffs include renames, additions, and deletions When the preflight report is generated Then the report shows a human-readable diff with change_type, impacted_fields, and downstream impact summary And for each Critical item, a remediation step is generated with concrete actions (e.g., add coercion rule, update mapping for new attribute, replace deleted tag) And selecting a remediation opens the relevant configuration panel pre-populated with context And re-running preflight after completing all remediations updates the health score and removes resolved blockers

Scope Trimmer

Requests only the minimum OAuth scopes needed for your chosen use cases and explains why—building security trust and speeding approvals. Less access friction means faster go‑live and happier admins.

Requirements

Use-case Driven Scope Selector
"As a workspace admin, I want to choose my exact EchoLens use cases and get only the minimal scopes required so that I can comply with least‑privilege policies and accelerate security approvals."
Description

Provide a UI where admins or PMs select EchoLens use cases (e.g., “Read Zendesk tickets read-only,” “Create Jira issues,” “Sync Linear statuses”). The system maps these selections to the minimal set of provider-specific OAuth scopes, automatically deduplicating across integrations and environments. Selections are versioned, persist per workspace, and support staging vs production configs. The selector exposes pre-validated recipes maintained by security, allows custom use case toggles, blocks combinations that violate least-privilege, and surfaces which data objects will be accessed and at what permission level for transparency and approvals.

Acceptance Criteria
Minimal Scope Mapping per Selected Use Cases
Given an admin opens the scope selector for Workspace W and selects the following use cases: "Read Zendesk tickets (read-only)", "Create Jira issues", and "Sync Linear statuses" When the system computes required OAuth scopes Then the proposed scopes contain only the minimal provider-specific scopes required for the selected use cases for Zendesk, Jira, and Linear And no additional scopes beyond those required by at least one selected use case are present And each scope entry shows a "Why needed" explanation listing the linked use case(s) And the total scope count equals the size of the unique minimal set across providers
Cross-Integration and Environment Scope Deduplication
Given a workspace with both Staging and Production environments and multiple selected use cases that require overlapping scopes within a provider When viewing the proposed scopes for each environment Then each environment shows a deduplicated set of scopes per provider (no duplicates) And enabling an additional use case that requires an already-included scope does not increase the scope count for that environment And scope sets are calculated independently per environment (Staging changes do not alter Production scopes)
Per-Workspace Persistence and Versioning with Rollback
Given a workspace where an admin saves a set of selected use cases and scopes with a version note "Initial" When the admin returns to the selector or refreshes the page Then the previously saved selections are persisted and shown as the active configuration And a version history lists the saved configuration with a version ID, author, timestamp, and note And when the admin rolls back to a prior version, the active selections and proposed scopes revert to match that version and are saved as a new version entry
Staging vs Production Configuration Separation
Given both Staging and Production environments are enabled for a workspace When an admin modifies use case selections in Staging Then Production selections, scopes, tokens, and history remain unchanged And the UI clearly indicates the active environment context And attempting to apply a Staging token or scope set to Production is blocked with an explanatory error
Pre-validated Security Recipes Enforcement
Given the selector lists pre-validated recipes maintained by Security with recipe name, version, and last-reviewed date When a non-security user attempts to edit a recipe’s scope mappings Then direct edits are disallowed and only on/off toggling of the recipe is permitted And only users with SecurityAdmin role can create or update recipe definitions and versions And when a recipe version is updated by Security, affected workspaces see the new version labeled and a diff summary prior to accepting the update
Custom Use Case Toggles with Least-Privilege Guardrails
Given an admin defines or enables a custom use case that includes a scope broader than policy allows for the selected provider (e.g., write when only read is permitted) When the admin attempts to save the configuration Then the save action is blocked and an error explains which scope violates least-privilege and suggests compliant alternatives And clearing or narrowing the offending scope enables Save And a policy audit entry records the blocked attempt with user, time, environment, and scope details
Data Access Transparency and Approval Summary
Given selected use cases for one or more providers When viewing the transparency panel Then for 100% of proposed scopes, the panel lists the associated data objects (e.g., tickets, comments, projects, issues) and the permission level (read, write, admin) And the panel shows a per-provider summary of objects accessed and permission levels suitable for approval review And the list updates immediately when use case toggles are changed
Scope Justification Generator
"As a security reviewer, I want clear justifications for each scope so that I can verify necessity and approve access with confidence."
Description

Generate human-readable explanations for each requested scope, detailing why EchoLens needs it, the exact actions performed, data touched, and links to provider documentation. Provide risk classification (read/write), data retention notes, and alternatives when available. Render inline during consent and in shareable artifacts, with support for localization and custom organization notes. Maintain a mapping library with versioning so justifications track product changes over time.

Acceptance Criteria
Inline Consent Justifications
Given a consent screen where EchoLens requests provider scopes and a mapping library exists When the consent modal opens Then each requested scope renders an expandable section containing: a plain-language purpose (≤200 chars), explicit action verbs, data categories touched, risk classification (read/write), a data retention note, and a link to the provider’s official scope documentation And sections default to collapsed and support individual and global expand/collapse And organization custom notes (if configured) render in the header and per-scope where applicable And the consent view renders in ≤300 ms for up to 20 scopes on median hardware And the layout meets WCAG 2.1 AA for labels, focus order, and aria-expanded states
Completeness Guardrail
Given N requested scopes When the justification generator runs Then 100% of requested scopes map to a justification entry by provider+scope id And if any scope lacks a mapping, the consent UI blocks submission and displays “Missing justification for scope: {provider}:{scope}” And an error is logged with severity=warn including the list of missing scopes And the export API returns HTTP 422 with details when mappings are incomplete
Provider Docs & Safer Alternatives
Given each requested scope has a documentation URL and optional safer alternatives in the mapping When the justification is rendered Then the documentation link is visible, opens in a new tab, and returns HTTP 200 within 2 seconds at generation time And if a read-only alternative exists for a write scope, the alternative scope id and capability tradeoff are displayed And if no alternative exists, “No safer alternative available” is shown
Risk Classification & Retention Disclosure
Given a scope’s mapping entry includes risk classification and retention metadata When the justification is generated Then the scope is labeled “Read” or “Write” based on enumerated actions And a retention statement is shown with one of: “No data stored”, or “Stored for {duration} in {store} for {purpose}” And if retention > 0, a link to the retention policy page is included
Shareable Artifact Export (HTML/PDF)
Given an authorized admin views the consent summary When “Export Justifications” is triggered Then a version-pinned, read-only HTML URL and a PDF file are generated within 2 seconds And artifacts include organization name/logo, environment (prod/sandbox), timestamp, locale, and custom organization notes And artifacts are immutable and retrievable by version id for at least 12 months And the HTML artifact achieves Lighthouse accessibility score ≥ 90
Localization and Fallback
Given supported locales [en, es, fr, de, ja] When a user’s preferred locale is set or detected Then all justification text, labels, dates, and numbers render in that locale And if a string is missing in the locale, English is used and a non-blocking “Translated from English” notice appears And export artifacts preserve the selected locale
Mapping Library Versioning & Auditability
Given a mapping library storing provider:scope → justification entries with semantic versioning When any justification text, actions list, data categories, risk, retention, or links change Then a new mapping version is created with changelog, author, timestamp, and reason And existing artifacts and consent records reference their original version And a diff view shows per-scope changes between versions And version history is retained for ≥ 24 months
Real-time Scope Diff & Impact Preview
"As an admin, I want to see exactly how my selections change the requested scopes so that I can make informed, low-risk choices before consenting."
Description

Show a dynamic diff that updates as use cases are toggled, highlighting newly added or removed scopes, net permission changes, and associated risk impact. Display which features will be enabled or disabled by the change and flag high-risk additions with warnings and required acknowledgments. Provide a rollback preview and save draft capability before initiating OAuth.

Acceptance Criteria
Real-time Diff on Use Case Toggle
Given a user has loaded the Scope Trimmer with an existing baseline of requested scopes When the user toggles a use case on Then the diff panel updates within 500 ms to display all newly added scopes grouped under "Added" with a count badge And shows net permission change as a positive integer And each scope chip includes a short description and risk level When the user toggles a use case off Then the diff panel updates within 500 ms to display all newly removed scopes grouped under "Removed" with a count badge And shows net permission change as a negative integer And only scopes impacted by the last toggle are visually highlighted for 2 seconds
Risk Impact Calculation and Highlighting
Given the baseline aggregate risk for the current scope set is displayed When the diff includes added scopes Then the aggregate risk impact is recalculated and shown as Low/Medium/High with color coding and delta indicators within 500 ms And each high-risk added scope is tagged "High Risk" with a tooltip explaining the risk rationale And each scope includes a link to official documentation When the risk calculation service fails Then a non-blocking error banner appears and risk badges show "Unknown" And Save Draft remains enabled while Continue to OAuth is disabled
Feature Enable/Disable Mapping
Given feature-to-scope mappings are configured When the user toggles use cases Then the Features Affected list updates to show Enabled and Disabled sections with item counts within 500 ms And each feature item lists the scope dependency causing the change And hovering a feature highlights the related scopes in the diff
High-Risk Additions Warning and Acknowledgment Gate
Given at least one added scope is classified High Risk When the user attempts to continue to OAuth Then a confirmation modal lists the high-risk scopes and their implications And the Continue action is disabled until the user checks an acknowledgment and enters their initials And the acknowledgment is timestamped and stored for audit with user ID and scope list When no high-risk scopes are present Then no confirmation modal is shown and the user can proceed without acknowledgment
Rollback Preview of Scope Changes
Given a pending diff exists relative to a previously authorized scope set When the user opens Rollback Preview Then the UI shows Before and After scope sets with Added and Removed tabs and net change And the Features Affected list reflects the rollback state And selecting "Create Rollback Draft" creates a draft restoring the previous scope set without initiating OAuth And closing the preview returns the user to the current diff without losing changes
Save Draft and Resume
Given the user has unsaved changes to use case selections When the user clicks Save Draft Then the current selections, diff, risk state, features affected, and acknowledgment progress are persisted for the workspace and integration And a "Draft saved" confirmation appears within 1 second When the user returns to Scope Trimmer for the same integration Then the draft automatically loads with the same UI state And starting OAuth or creating a new draft prompts to overwrite or discard the existing draft
Proceed Controls and Validation
Given a pending diff exists Then the Continue to OAuth control is disabled if risk data is Unknown, required acknowledgments are incomplete, or validation errors are present When all validations pass and any acknowledgments are complete Then the control enables and displays the total number of scopes to be authorized And clicking Continue logs an audit event capturing user, timestamp, scopes, risk state, and acknowledgments before redirecting to OAuth
Cross-Provider Permission Normalization
"As a product owner, I want a consistent way to understand permissions across vendors so that I can evaluate access without learning each provider’s scope taxonomy."
Description

Normalize provider-specific scopes into a common permission model (read, write, admin by resource type) while retaining precise underlying scope strings per provider (e.g., Google, Microsoft, Zendesk, Intercom, Jira, Linear). Present a unified view to users but request the exact scopes at OAuth time. Maintain normalization rules and automated tests to ensure parity as providers evolve.

Acceptance Criteria
Unified Permission View Across Providers
Given a workspace has connected at least two providers with overlapping resource types When the user opens the Permissions summary Then each resource type is shown once with a normalized level (read, write, admin) And the normalized level reflects the union of provider-granted capabilities without duplicating providers And a Details toggle reveals the exact provider scope strings grouped by provider And no provider-specific scope strings appear in the normalized labels or levels
Exact OAuth Scope Requests Per Provider
Given a user selects normalized permissions for a provider connection When the OAuth consent flow is initiated for that provider Then the request includes exactly the provider scope strings mapped to the selected normalized permissions And the request includes no additional provider scopes beyond the mapping And the requested scopes and mapping rationale are recorded in an audit log with timestamp, user, provider, and connection ID
Round-Trip Normalization Integrity
Given a set of provider scopes is received from an OAuth callback When the system normalizes them to resource-level permissions and re-expands back to provider scopes Then the re-expanded scope set equals the original mapped scope set 1:1 And any provider scopes not covered by mappings are retained as opaque values and do not increase normalized permission levels And integrity results are logged for traceability
Handling Unknown or New Provider Scopes
Given a provider introduces a new scope not present in the ruleset When the system encounters the scope during discovery or callback Then the scope is marked Unmapped with provider and scope identifier And the normalized permissions default to the most conservative interpretation (no elevation) And a rule-authoring task is created with severity High And CI parity checks fail with a clear message until a mapping is added
Automated Parity and Regression Tests
Given official provider scope catalogs and canonical fixture sets exist in the repository When CI runs on each commit and nightly Then coverage reports show 100% mapping coverage for required scopes per provider (or explicit Unmapped entries) And snapshot tests confirm normalized outputs for canonical fixtures match expected values And contract tests against latest provider metadata pass with no breaking changes And failures block releases and post results to the team channel
Minimal Permission Justification UI
Given a user reviews requested permissions before OAuth When viewing the explanation panel Then each normalized permission displays a plain-language justification tied to the selected use case(s) And the exact provider scope strings are listed per provider under that permission And no requested scope lacks a linked use case And a Minimal indicator is shown if removing any requested scope would break a selected use case; otherwise an Excess indicator is shown
Rule Versioning, Diff, and Re-consent
Given normalization rules are updated to a new version When an existing connection using an older ruleset is loaded Then the system computes and displays a diff of provider scopes between versions And if additional scopes are required, the admin is prompted for re-consent with an itemized change list and justifications And if scopes are reduced or unchanged, no re-consent is triggered And the audit log records previous version, new version, diff summary, actor, and timestamp
Approval Bundle Export (Link/PDF/JSON)
"As a security reviewer, I want an exportable approval bundle so that I can review and record the request in my governance tools without duplicating work."
Description

Produce a shareable approval package containing the selected use cases, minimized scopes, justifications, risk summary, data flow diagram, and environment details. Offer a secure, expiring link for reviewers, plus downloadable PDF and machine-readable JSON for governance and ticketing systems. Include organization identifiers, requested-by, timestamp, and a checksum of the consent screen to aid audits.

Acceptance Criteria
Secure Expiring Review Link Generation
- Given an admin user with export permissions and a completed scope selection for N use cases, When they generate an approval bundle with link TTL set to 7 days, Then the system produces an HTTPS link containing a URL-safe token of at least 22 characters and returns 201 Created. - Given the secure link is generated, When a recipient accesses it before expiry, Then the bundle metadata and download options (PDF, JSON) are visible without requiring platform login if "public link" was selected, else login is required. - Given the link has expired after exactly 7 x 24 hours UTC, When any user requests it, Then the system returns 410 Gone and no bundle contents are leaked. - Given an admin revokes the link, When recipients try the link within 60 seconds of revocation, Then access is blocked with 410 Gone.
Complete PDF Bundle Content and Layout
- Given an approval bundle is generated, When the PDF is downloaded, Then it contains sections: Selected Use Cases, Minimized OAuth Scopes, Justifications per Scope, Risk Summary, Data Flow Diagram, Environment Details, Organization Identifiers, Requested By, Generated Timestamp (UTC ISO 8601), Consent Screen Checksum. - Given the PDF is opened, Then text is selectable and searchable and includes page numbers and a header with org name and bundle version. - Given the data flow diagram is included, Then it renders at minimum 300 DPI or as vector without pixelation on A4/Letter at 100% scale. - Given a known sample export, When the PDF is validated, Then the file opens without errors and passes PDF/A-2b validation.
Machine-Readable JSON Bundle Schema
- Given an approval bundle is generated, When JSON is requested, Then the response has Content-Type "application/json; charset=utf-8" and a filename suffix ".json". - Given the JSON is downloaded, Then it validates against schema version "com.echolens.approval-bundle.v1" and includes required fields: use_cases[], scopes_minimized[], justifications[], risk_summary, data_flow_diagram, environment, org_identifiers, requested_by, generated_at, consent_checksum, bundle_version. - Given fields are present, Then timestamps are in UTC ISO 8601 with Z suffix; identifiers are strings; scopes are arrays of strings. - Given optional fields are omitted, When validation runs, Then the JSON still passes schema validation per documented defaults.
Checksum and Integrity Verification
- Given a bundle is generated, When the consent screen checksum is computed, Then it uses SHA-256 and is exposed as a 64-character lowercase hex string in both PDF and JSON. - Given the same consent screen content and scope configuration are unchanged, When a new bundle is generated, Then the consent checksum value remains identical. - Given the consent screen content changes, When a new bundle is generated, Then the consent checksum value changes. - Given an entire bundle file (PDF/JSON) is downloaded, Then an ETag or X-Content-SHA256 header is provided so recipients can verify file integrity.
Access Control and Audit Logging for Exports
- Given a user without Admin or Security role attempts to generate an approval bundle, When they hit the export endpoint, Then the system returns 403 Forbidden. - Given an authorized user exports a bundle, Then the system records an immutable audit log entry with: org_id, user_id, action=export_bundle, bundle_id, timestamp (UTC), link_ttl, outputs_selected, and requester IP. - Given a reviewer accesses the secure link, Then the access is logged with link_id, timestamp, IP, user-agent, and outcome (success/expired/revoked). - Given an admin requests the audit log for a bundle, When retrieved, Then it matches the export and access events recorded.
Environment and Organization Metadata Accuracy
- Given an export is initiated from a specific workspace and environment, When the bundle is generated, Then org_identifiers include org_id, org_name, workspace_id (if applicable), and environment in {production, staging, development}. - Given "requested_by" is captured, Then the bundle includes full name, email, and role of the requester. - Given the bundle is generated, Then "generated_at" includes an ISO 8601 UTC timestamp with Z suffix. - Given any required metadata fields are missing, When export is attempted, Then the UI blocks export with a clear validation message listing missing fields.
Data Flow Diagram Inclusion and Fidelity
- Given a data flow diagram exists for the selected use cases, When the bundle is generated, Then the PDF embeds the diagram as vector (SVG/PDF) or 300+ DPI raster and includes a caption and legend. - Given the JSON bundle is generated, Then it contains data_flow_diagram.image (base64), data_flow_diagram.format (svg|png), and data_flow_diagram.graph (nodes[], edges[]) for machine readability. - Given the diagram includes third-party processors, Then nodes are labeled with processor names and data categories; edges indicate direction and transport (e.g., HTTPS). - Given the diagram is missing, When export is attempted, Then the user is prompted to add or confirm a default diagram before export.
Runtime Least-Privilege Enforcement and Alerts
"As a platform engineer, I want runtime checks and alerts on scope usage so that we avoid privilege creep and catch misconfigurations before they become incidents."
Description

Enforce least-privilege at runtime by validating that issued tokens match the configured minimal scope set and by detecting over-scoped tokens. Provide proactive alerts when a call requires broader access than configured, with guided remediation to add a minimal new scope or adjust the use case. Log violations, block dangerous operations by default, and support safe fallbacks to keep EchoLens operational without unnecessary privilege.

Acceptance Criteria
Over-Scoped Token Detected During Runtime Request
Given a tenant has configured minimal scopes S for an integration And an incoming OAuth token T contains one or more scopes not in S When any EchoLens service authenticates a request with T Then the service classifies T as over-scoped And the request is permitted only if the operation’s required scopes are a subset of S And an over-scoped token alert with error_code=E_SCOPE_OVER_SCOPED is emitted to the tenant within 60 seconds And a violation log entry is written with tenant_id, client_id, operation, provided_scopes, configured_scopes, decision, timestamp
Missing Scope for Operation Triggers Proactive Remediation
Given a tenant has configured minimal scopes S for an integration And a request targets operation O that requires scope r not in S When the request is evaluated at runtime Then the operation is blocked by default with HTTP 403 and error_code=E_INSUFFICIENT_SCOPE And the response includes required_scopes=[r], provided_scopes=S, correlation_id And an in-app admin notification is raised within 5 seconds with a guided remediation to add only r (or adjust the use case) And upon admin approval, the OAuth re-consent flow is initiated and retried once automatically And a remediation audit record is created linking the alert, admin actor, and outcome
Violation Logging and Audit Trail Completeness
Given any over-scoped token detection or insufficient-scope block occurs When the event is logged Then the log record contains at minimum: timestamp (ISO-8601 UTC), tenant_id, environment, integration, client_id, operation, required_scopes, provided_scopes, decision (allowed|blocked), error_code (if any), alert_id (if any), remediation_status And sensitive token values (access_token, refresh_token) are never persisted And records are retained for at least 365 days and exportable via API as JSON and CSV And logs are searchable by tenant_id, error_code, and correlation_id within 2 seconds for the last 30 days
Default Blocking of Dangerous Operations with Safe Fallbacks
Given a policy matrix marks operations as dangerous (write, delete, or admin) and non-dangerous (read) And a request lacks required scopes or uses an over-scoped token When the request targets a dangerous operation Then the operation is blocked by default And a safe fallback is applied: the action is queued for up to 24 hours pending remediation, and the user is shown a non-blocking warning When the request targets a non-dangerous operation and required scopes are a subset of S Then the operation proceeds And an over-scoped token, if present, still generates an alert and log without elevating privileges
Admin Alerting and Notification Delivery
Given alert destinations (email and/or Slack webhook) are configured for the tenant When an over-scoped token is detected or a dangerous operation is blocked Then an alert is delivered to all active destinations within 60 seconds with summary, severity (High for dangerous blocks, Medium for over-scoped tokens with allowed operation), required vs provided scopes, affected operation, and remediation link And identical alerts are deduplicated per tenant and operation for 10 minutes And alert delivery status is recorded (delivered|failed) with retry up to 3 times with exponential backoff
Consistent Error Response for Scope Enforcement
Given any request is denied due to scope enforcement When the API returns the error Then the HTTP status is 403 And the JSON body includes fields: error_code (E_INSUFFICIENT_SCOPE or E_SCOPE_OVER_SCOPED), message (human-readable), required_scopes (array), provided_scopes (array), correlation_id, documentation_url And the correlation_id is also present in server logs And no sensitive token material is returned in the payload
Scope Request Audit Trail and Evidence Store
"As a compliance lead, I want a complete audit trail of scope requests and approvals so that I can satisfy audit requirements and investigate incidents."
Description

Maintain an immutable record of scope configurations, consent events, approvers, versions of mappings and justifications, and resulting tokens per environment. Support search, retention policies, and export to SIEM. Provide evidence snapshots suitable for SOC 2 and ISO audits, including who approved what, when, and why.

Acceptance Criteria
Immutable Audit Log per Environment
- On any of: scope config create/update, consent grant/revoke, approval record, token issue/revoke, or mapping version change, the system writes a new append-only audit record to the environment-specific log. - Each record includes: event_type, environment, timestamp (UTC ISO 8601), actor_id, actor_role, approver_id (if applicable), scopes[], justification, mapping_version, client_id/app_id, token_id (if applicable), request_id, source_ip, and hash = H(prev_hash + record_payload). - UI/API attempts to modify or delete individual audit records are rejected with 405; only retention purge is permitted and is itself logged as a purge event. - An integrity-check endpoint returns Pass when the hash chain validates across all records in range; any tampering causes Fail and identifies the first broken link id. - Per-environment event timestamps are monotonic with max clock skew tolerance of 2 minutes; out-of-order writes beyond tolerance are quarantined and alerted.
Scope Mapping Versioning and Traceability
- Editing scope-to-use-case mappings or justifications creates a new immutable mapping_version with incremented version number, editor_id, change_summary, and a generated diff. - Approvals and issued tokens persist the mapping_version id effective at decision/issuance time. - API/UI can retrieve any mapping_version and render a diff between two versions; responses include who/when/why metadata. - Attempts to overwrite or delete a prior mapping_version return 409 and leave history intact. - A lineage view for any approval shows mapping_version, approver, justification, and resulting token(s).
Consent Event and Token Linkage
- On admin consent via OAuth, the system records a consent_event with client_id, account/tenant id, environment, scopes_granted[], consentor identity, auth method, timestamp, and request_id. - Resulting token issuance writes a token record linked to the consent_event and approval (where applicable), including token_id, subject, scopes[], issued_at, expires_at, and environment. - On consent revocation, a revoke event is recorded and linked; new token issuance for that client/environment is blocked until reconfirmed consent. - Querying by token_id returns full lineage: approval -> consent_event -> token record. - All consent and token events appear in audit search within 5 seconds p95 of occurrence.
Advanced Audit Search and Filtering
- UI and API support filters: date_range, environment, event_type, scope name, approver_id, actor_id, client_id, token_id, mapping_version, and full-text search over justification/comments. - For a 30-day window with <=100k events, the first page returns within <=2s p95; supports cursor-based pagination with stable sort by timestamp desc. - Search results can be exported to JSON and CSV; exports include all displayed fields and UTC timestamps. - Access control enforces that only users with AuditViewer role can search/export; unauthorized requests return 403 and are logged as security events. - Searches and exports are themselves recorded as audit events with requester_id and query parameters (redacting secrets).
Configurable Retention and Legal Hold
- Admin can configure per-environment retention (180–3650 days) for audit records and token metadata via UI/API; changes require approver acknowledgement and justification. - A daily purge job deletes records beyond retention and logs a purge_summary event with counts and time range; purged data is irrecoverable. - Legal hold can be applied by tag or time range to exclude records from purge; holds are auditable, require approver, and block deletion until removed. - Attempts to purge held records are skipped and counted in the purge_summary; removal of a hold is logged with who/when/why. - A compliance report endpoint returns current retention configs, holds, last purge status, and next scheduled purge time.
SIEM Export and Streaming Integrations
- System supports continuous streaming to SIEM via HTTPS JSON webhook and Syslog TCP (CEF); each event includes idempotency_key, event_type, environment, ts, actor, approver, scopes, token_id (if any), and integrity hash. - Delivery guarantees: at-least-once with retries using exponential backoff up to 24h; dead-letter metrics are exposed; duplicate suppression supported via idempotency_key. - Batch backfill API exports any time window up to 1M events within 15 minutes, chunked and resumable. - Outbound payloads can be HMAC-SHA256 signed; connection requires TLS 1.2+; a “Send test event” action validates configuration and surfaces errors. - Export failures and configuration changes are captured as audit events and surfaced in health alerts.
Evidence Snapshot for SOC 2/ISO Audits
- Authorized users (AuditAdmin role) can generate a time-bounded evidence snapshot that includes approvals, consent events, mapping_versions, and token records with who/what/when/why fields. - Snapshot output is a deterministic ZIP containing JSON data, a manifest, and a Merkle root integrity file; the snapshot is stored WORM for its retention period and is downloadable. - A verifier endpoint recomputes the integrity proof from the manifest and returns Pass/Fail; verification attempts are logged. - Snapshot requests, parameters, requester identity, and resulting hashes are recorded as audit events. - Snapshot generation of a 90-day period with <=500k events completes within 10 minutes p95 or returns progress with resumable download.

Sync Preview

Runs a safe dry‑run that shows sample records, expected volume, deduping rules, and theme coverage before activation. Gives teams confidence that the right data will flow—no surprises, no data mutation in source tools.

Requirements

Read-only Source Authorization
"As a security-conscious CX lead, I want the preview to use read-only access so that there is no chance of changing or deleting data in our source tools."
Description

Validates and enforces read-only scopes for all connected sources during Sync Preview. Performs non-mutating permission checks and dry-run API calls to ensure the preview cannot alter, acknowledge, or delete source records. Surfaces scope mismatches with guided remediation. Integrates with the EchoLens connector framework and secrets vault to store tokens securely, tag preview sessions as read-only in logs, and verify scope parity before activation. Outcome: zero risk of data mutation and clear operator confidence prior to activation.

Acceptance Criteria
Enforce Read‑Only OAuth Scopes During Preview
Given a user connects a source via OAuth When Sync Preview is initiated Then the connector validates that only read‑only scopes are present and no write/modify/delete scopes are granted And if any write scope is detected, the preview for that source is blocked and a scope mismatch error lists each offending scope And the error provides a “Recheck scopes” action that revalidates within 5 seconds after re‑consent
Block Non‑Read HTTP Methods and Side‑Effect Endpoints
Given Sync Preview executes connector requests When a request would use POST, PUT, PATCH, DELETE, or a GET endpoint flagged as side‑effecting in the allowlist Then the platform blocks the call before egress, records an audit event mutation_blocked, and continues without altering the source And upon completion, internal metrics show 0 successful write attempts and vendor API logs reflect 0 write calls from the EchoLens client during the preview window
Guided Remediation for Scope Mismatch
Given a scope deficit or surplus is detected for a source When the user opens the source in Preview Then a remediation panel displays missing and disallowed scopes, provider‑specific update steps, and a one‑click Open consent flow button And after re‑authentication, validation runs automatically and, if compliant, the source status changes to Ready (Read‑only verified) without page refresh
Secrets Vault Storage and Redaction
Given any token or credential is exchanged for Preview When the token is persisted Then it is stored in the EchoLens secrets vault with KMS encryption at rest and never logged in plaintext And when logs are emitted during Preview, tokens/headers are redacted and every log line includes session_tag=read_only_preview and source_id And a security audit record exists for the preview session with read_only=true and last_validated_at timestamp
Scope Parity Check Before Activation
Given Preview has completed for one or more sources When the user clicks Activate Sync Then the system verifies that effective scopes per source are identical to or stricter than those used in Preview and introduce no write scopes And if any source adds a write scope or widens scopes beyond Preview, activation is blocked with a list of impacted sources and required re‑consent steps And if all sources pass, activation proceeds
Per‑Source Isolation and Safe Failure
Given multiple sources are connected When one source fails read‑only validation Then its preview is marked Blocked — Scope mismatch while compliant sources continue and complete Preview And the overall summary shows per‑source readiness and explicitly indicates 0 data mutations occurred across all sources
Sample Record Explorer
"As a product manager, I want to preview sample records with mappings and redactions so that I can verify the data looks correct before I enable sync."
Description

Generates a representative, randomized sample of records from each connected source within the selected lookback window. Displays normalized fields with source provenance, field-level PII redaction, and schema mapping to EchoLens entities (message, ticket, review). Supports pagination, search, filters, and raw/parsed toggles so users can verify parsing quality and field mappings. Integrates with the ingestion layer and schema registry to auto-detect fields, handle source-specific encodings, and ensure parity with live ingestion.

Acceptance Criteria
Randomized Sampling per Source within Lookback
Given at least two data sources are connected and a lookback window of X days is selected When the user opens the Sample Record Explorer Then the system displays a randomized sample from each source that has at least one record within the window And the default sample size is 50 records per source (configurable between 10 and 200) And no sample request writes to or mutates any source system And the sample contains no records dated outside the selected lookback window
Normalized Fields, Provenance, and Entity Mapping
Given sample records are displayed in the explorer When a record is viewed in parsed mode Then the UI shows normalized fields: entity_type, text/body, title/subject (when present), created_at, author, source, external_id, and permalink (if available) And each normalized field shows the source field name used for mapping (e.g., zendesk.comment.body -> text) And provenance displays source connector name, account/instance, and a deep link via permalink or external_id when available And missing source fields are shown as null while still showing the intended mapping And entity_type is correctly assigned as one of [message, ticket, review] according to mapping rules
Field‑level PII Redaction in UI
Given PII redaction is enabled by policy When sample records contain emails, phone numbers, credit card numbers, IP addresses, or government IDs in any field Then those values are masked in both list and detail views using format-preserving tokens (e.g., john***@example.com, ****-****-****-1234) And a redaction badge is displayed on each affected field with a tooltip indicating the redaction rule And redacted values are excluded from the explorer's search index And raw and parsed views both apply the same UI redaction while the underlying stored payload remains unchanged
Pagination, Search, and Filter Controls
Given the sample set contains more records than a single page When the user interacts with pagination Then page sizes of 25, 50, and 100 are available and next/previous navigation works without skipping or duplicating records And free-text search matches across normalized title/subject and text/body fields And filters include Source (multi-select), Entity Type (message/ticket/review), and Date (subset within the selected lookback) And applying or clearing any single control updates the results within 1s median and 2s p95 And resetting all controls restores the original randomized sample
Raw/Parsed Toggle and Encoding Handling
Given a sample record is selected When the user toggles between Raw and Parsed views Then Raw shows the original source payload (pretty-printed) with the same UI PII redaction applied And Parsed shows normalized fields with mapping labels and values And emojis, non‑Latin characters, and HTML entities render correctly (e.g., 😊, Müller, and &amp; decoded to & in parsed text) And HTML markup is sanitized/stripped in parsed text while preserving meaningful content And the same record external_id/permalink is shown in both views for reference
Schema Auto‑Detection and Ingestion Parity
Given the schema registry and ingestion layer are reachable When the explorer loads a sample set Then all fields present in the sample are discovered via the schema registry and listed in the mapping panel And any field not present in the registry is flagged as "new" for review And for a 100‑record validation subset, field mappings in the explorer match live ingestion mappings 100% by field name and normalized value And any mismatch triggers a visible parity warning showing the field name and an example record
Performance and Read‑Only Guarantees
Given up to 7 connected sources and the default sample size When the user loads the explorer or paginates, searches, or filters Then initial sample generation completes within 5s p95 and 2s p50 And subsequent interactions complete within 2s p95 and 800ms p50 And all operations use read‑only permissions against sources with no POST/PUT/PATCH/DELETE requests executed
Volume & Runtime Estimator
"As an operator, I want to see how much data will be synced and how long it will take so that I can plan activation without disrupting other workloads."
Description

Calculates expected record volume and processing time for initial and incremental syncs based on historical rates, selected sources, filters, and provider rate limits. Presents per-source and total counts, expected backfill duration, pagination/rate-limit strategy, and schedule impact. Simulates API quota consumption and flags risks such as weekly caps or throttling windows. Integrates with connectors to fetch metadata endpoints and with the scheduler to propose safe run windows.

Acceptance Criteria
Initial Backfill Volume Estimate per Source
Given one or more data sources are selected and a backfill date range is specified, When the user runs the Sync Preview estimator, Then the estimator queries only metadata/count endpoints and performs no write/mutation calls to the sources. Given the estimator completes successfully, When results are displayed, Then per-source estimated record counts and a total are shown, each with a confidence score between 0.0 and 1.0 and a timestamp of estimation. Given provider rate limits, average response times, and page sizes are known or inferred, When the estimator calculates duration, Then per-source and total backfill durations are displayed in hh:mm with a 20% 95th-percentile latency buffer included. Given a staging dataset of ≥10,000 records per source for validation, When the first production backfill is compared to the estimate, Then the per-source estimated counts are within ±10% of the actual discovered records. Given up to 5 sources with a combined estimate ≤500,000 records, When the estimation runs, Then it completes within 15 seconds end-to-end.
Incremental Sync Estimate with Filters Applied
Given the user has configured filters (e.g., date_since, labels/tags, language), When the estimator runs, Then the projected incremental volume per run and per day is calculated using the trailing 14-day ingestion rates adjusted by the filters and displayed numerically. Given a chosen schedule frequency (e.g., hourly, every 4 hours, daily), When the estimator runs, Then the expected runtime per incremental run and aggregate daily runtime are displayed in mm:ss and hh:mm respectively. Given historical data exists for the selected filters, When comparing estimates to the last 7 days of actuals, Then the daily volume estimate is within ±15% MAPE. Given filters exclude all historical records, When the estimator runs, Then the projected volume is 0 and the UI clearly states that filters yield no matching records. Given no historical data for a source, When the estimator runs, Then it marks the estimate as low confidence and documents the assumption basis used (e.g., global baseline rate).
Rate-Limit and Pagination Strategy Simulation
Given provider rate limits (per-minute/per-day) and pagination parameters are available, When the estimator simulates requests, Then it outputs per-source pages count, requests per minute, expected backoff wait time, and total request count without exceeding documented limits. Given the provider responds with 429 in simulation or historical logs indicate throttling windows, When the estimator computes strategy, Then it applies exponential backoff (jittered) and shows the expected added wait time per window. Given rate limits are unknown, When the estimator runs, Then it uses conservative defaults (RPM=30, concurrency=1, page_size=50), marks confidence as low, and clearly labels the assumptions used. Given the proposed pagination and concurrency, When the estimator produces a plan, Then it includes the selected strategy parameters (concurrency, page_size, backoff curve) and an estimated completion timeline per source.
API Quota Consumption and Risk Flags
Given provider weekly/monthly API quotas, When the estimator computes consumption, Then it shows expected quota usage per source and overall for the backfill and the first 7 days of incremental runs. Given estimated consumption ≥80% of a provider quota, When results are displayed, Then a High Risk warning is surfaced with the specific source, expected consumption percentage, and recommended mitigations. Given estimated consumption ≥100% of a provider quota, When results are displayed, Then a Blocker alert is shown and activation is prevented until schedule or scope is adjusted. Given provider-defined throttling/maintenance windows, When the proposed schedule intersects such windows, Then a Warning flag is shown with the affected windows and expected slowdown in minutes.
Schedule Impact and Safe Run Window Proposal
Given the workspace timezone and existing scheduled jobs, When the estimator runs, Then it proposes at least three safe run windows in the next 24 hours that avoid >20% overlap with existing jobs' peak utilization windows. Given the estimated backfill duration, When proposing windows, Then each proposed window ensures completion at least 10 minutes before the next scheduled job that could contend for the same resources. Given quiet hours are configured, When proposing windows, Then no proposal falls within quiet hours unless explicitly overridden and marked as such. Given the user-selected schedule (e.g., hourly), When the estimator validates feasibility, Then it confirms the run can complete within the cadence; otherwise it flags Insufficient Window with the computed deficit time.
Deduping Rules Effect on Volume
Given deduping rules are configured (content hash, external_id, thread/conversation grouping), When the estimator runs, Then it displays pre-dedupe and post-dedupe estimated counts per source and overall, with percent reduction. Given post-dedupe reduction exceeds 50% for any source, When results are displayed, Then a Possible Over-Dedupe Risk warning is shown with the rule(s) contributing most to the reduction. Given a validation dataset with prior dedupe outcomes, When comparing estimates to actuals, Then post-dedupe count estimates are within ±5% for that dataset. Given the Sync Preview is a dry run, When showing dedupe effects, Then no changes are written to source systems and only sample record identifiers or hashes are displayed for illustration.
Connector Metadata Fallback and Error Handling
Given a connector metadata endpoint errors (4xx/5xx) or times out, When the estimator retries, Then it performs up to 3 retries with exponential backoff and jitter before falling back to heuristic estimation. Given fallback is used, When results are displayed, Then the source is marked Low Confidence with a clear explanation of which endpoints failed and what assumptions were applied. Given one or more sources fail to estimate, When the estimator completes, Then successful sources still return estimates, failures are isolated, and a structured error report is available for download. Given strict dry-run mode, When estimating across all sources, Then the system performs no write operations to any provider APIs and completes within 30 seconds even if one source fails.
Deduping Rules Simulator
"As a data owner, I want to test deduping rules and see their impact so that I avoid data inflation or loss when I go live."
Description

Emulates deduplication and merge logic during preview using configurable keys (e.g., source+external_id, email+timestamp window) and fuzzy thresholds. Visualizes potential duplicates, collision groups, and the resulting unique count, with what-if analysis for rule adjustments. Reports impact metrics (kept vs. dropped, merge depth) and preserves source traceability. Integrates with identity resolution modules and reuses the exact rules in live sync to ensure preview-to-live parity.

Acceptance Criteria
Configurable Deduping Keys Preview
Given a preview dataset and user-selected keys (e.g., source+external_id; email+timestamp window=24h), When the simulator runs, Then potential duplicates are identified strictly per the selected keys and window. Given two records share the same source and external_id, When preview executes, Then they appear in the same collision group. Given two records share the same normalized email within the defined timestamp window, When preview executes, Then they appear in the same collision group. Given the simulator completes, When it displays the predicted unique count, Then that count equals the number of records minus predicted merged records for the frozen dataset. Given a dataset of at least 50,000 records, When preview runs with key-only rules, Then results return within 60 seconds on standard tier infrastructure.
Fuzzy Matching Threshold Simulation
Given a fuzzy name+subject matcher is enabled, When the user sets a threshold T in [0.00,1.00] with step 0.01, Then only pairs with similarity ≥ T are considered duplicates. Given the user adjusts T, When T increases, Then the predicted duplicate pair count does not increase and the unique count does not decrease. Given a fixed dataset and threshold T, When preview is rerun, Then predicted pairs and groups are identical across runs (idempotent). Given a change of T that alters groupings, When the UI updates, Then delta metrics (pairs added/removed, groups split/merged) render within 500 ms. Given benchmark test data with known fuzzy matches, When T=0.80, Then the simulator achieves ≥95% precision and ≥85% recall against the labeled set.
Collision Group Visualization and Drilldown
Given preview results are available, When the user views collision groups, Then each group shows a stable group ID, size, rule(s) that triggered, and a representative record. Given a group is opened, When the user drills down, Then at least 5 sample records are shown with full key fields and similarity scores; the user can open the full list when size >5. Given groups exist, When sorting by size or confidence, Then ordering updates correctly and deterministically. Given multi-source datasets, When a group contains cross-source records, Then sources are labeled per record and cross-source grouping is visually indicated. Given the user inputs a record ID, When search is executed, Then the containing group is returned within 1 second or a clear “not grouped” status is shown.
Impact Metrics and What‑If Analysis
Given preview completes, When metrics render, Then the following are displayed: total records, predicted unique count, kept vs dropped counts, number of groups, and merge depth distribution. Given rule adjustments are made, When the user applies changes, Then the metrics panel updates with absolute values and deltas (±) relative to the previous configuration. Given a dataset and rule set R1, When compared to rule set R2, Then a side‑by‑side diff shows changes in unique count, number of groups, average group size, and top 10 affected groups. Given export is requested, When the user downloads metrics, Then a CSV/JSON is produced containing all displayed metrics, timestamp, and rule configuration hash.
Source Traceability Preservation
Given any predicted merge, When viewing the group, Then every member shows source system name, source record ID, and ingestion timestamp. Given a representative (kept) record is selected by tie‑breaker logic, When viewing details, Then the exact tie‑breaker criteria and their evaluations are shown. Given a group is exported, When lineage CSV is generated, Then it includes one row per original record with its group ID, kept/dropped flag, and pointer to the representative record. Given preview only mode, When external APIs are called, Then no write/mutation endpoints are invoked (read‑only calls only) and an audit log confirms zero writes.
Identity Resolution Integration
Given identity resolution is enabled, When preview runs, Then person/account IDs supplied by the identity module are used as additional grouping features per configuration. Given identity resolution is disabled, When preview runs, Then no identity features are used and results differ only by those features’ absence. Given mock identity responses are injected, When preview runs, Then the simulator produces deterministic groups matching the mocked identities. Given network failure to the identity service, When preview runs, Then it degrades gracefully with a clear warning and excludes identity features from grouping without failing the entire preview.
Preview‑to‑Live Rule Parity and Snapshot
Given a rule configuration is finalized, When the user saves it, Then a signed, immutable snapshot ID (hash) is created and displayed. Given the same frozen dataset and snapshot ID, When live sync is run in a staging harness, Then collision groups and unique count exactly match the preview (0 discrepancy). Given a live run uses the snapshot ID, When audit is performed, Then logs show the same rules, thresholds, and tie‑breakers as preview with the identical snapshot ID. Given preview mode, When the simulator executes, Then no data mutation occurs in any source tool and a “dry‑run” banner is present in all relevant views.
Theme Coverage Forecast
"As a PM, I want to see which themes the incoming data will cover and with what confidence so that I know if the feed will surface high-impact issues."
Description

Runs a lightweight clustering pass over the preview sample to estimate expected theme coverage, confidence score distribution, and blind spots once the sync is active. Renders a mini theme map with top clusters, exemplar phrases, and confidence histograms, and highlights low-confidence or out-of-vocabulary segments. Integrates with the EchoLens ML pipeline using constrained resources to keep previews fast and cost-efficient while approximating live behavior.

Acceptance Criteria
Preview Forecast Generates Mini Theme Map from 1,000-Record Sample
Given a user clicks "Run Preview" in Sync Preview for connected sources and the system selects a 1,000-record sample (or the maximum available up to the cap) When the lightweight clustering pass executes Then it completes within 45 seconds wall-clock at the 95th percentile And renders a mini theme map showing the top 10 clusters (or all clusters if fewer) And each cluster displays at least 3 exemplar phrases when available and a representative label And an overall theme coverage percentage is shown with assigned_count/total_sample denominator values And a confidence histogram per cluster and an aggregate histogram are displayed And no write operations are performed to any source tools (audit log shows 0 writes) And the model version and pipeline hash displayed match production versions
Highlights Low-Confidence and Out-of-Vocabulary Segments
Given preview results include per-segment confidence scores and vocabulary coverage metrics When any segment has a max confidence < 0.60 Then it is flagged as Low Confidence and the UI shows a summary badge with count and % of total sample And clicking the badge filters the segment list to only low-confidence items When any segment has OOV rate > 70% or language is unknown Then it is flagged as Out-of-Vocabulary and highlighted distinctly from Low Confidence And CSV export includes per-segment flags for Low Confidence and OOV
Renders Confidence Score Distributions and Histograms
Given cluster and aggregate confidence scores are computed for the preview When rendering histograms Then the aggregate histogram uses 10 fixed bins spanning [0.0, 1.0] And each cluster histogram reuses the same bin edges for direct comparability And tooltips display bin range and count (and % of cluster/sample) And a toggle switches histogram values between absolute counts and percentages And a download control exports the binned distributions as CSV with headers: bin_start, bin_end, count, percent
Preview ML Pass Uses Constrained Resources and Mirrors Live Pipeline
Given a preview ML job is scheduled When the job runs Then it uses <= 2 vCPU, <= 1 GiB RAM, and <= 60s time budget And it uses the same tokenizer and model weights version tag as production (version tag equality displayed) And only beam width and sample cap differ from live, with deviations disclosed in an "Approximation" note And if any constraint is exceeded, the job aborts gracefully, surfaces an error banner with retry guidance, and does not render partial results
Deduplication-Aware Coverage and Metrics
Given deduping rules are enabled in Sync Preview When the preview sample is drawn Then duplicates are removed before clustering And the UI displays the dedupe rate (duplicates_removed/raw_sample) and adjusts coverage denominators accordingly And if dedupe rate > 20%, a warning badge appears linking to deduping settings And coverage metrics remain consistent across repeated runs on the same fixed sample within ±1% absolute variance
Small Sample Handling and Forecast Accuracy Disclosure
Given the available recent records are limited When the sample is < 300 items or < 10% of the last 30 days' traffic Then a "Low Sample Size" banner appears showing the exact count and recommended actions And histograms collapse to 5 bins if total assigned < 200 And the forecast displays a 95% confidence interval for theme coverage computed via bootstrap (1,000 resamples) And if sample < 50, the forecast visuals are disabled and an explanatory message is shown instead
Accessible, Performant Forecast UI with Safe Dry-Run Guarantees
Given a user opens the Preview Forecast results When content loads Then first chart paint occurs within 2 seconds after data readiness on a standard laptop under simulated 3G, at the 95th percentile And all controls are keyboard-navigable with visible focus order and ARIA labels And chart and text colors meet WCAG 2.1 AA contrast (>= 4.5:1 for text, >= 3:1 for graphical elements) And a "Dry Run" indicator is visible and network activity shows zero calls to source write endpoints And transient ML job errors are retried up to 3 times with exponential backoff and a clear retry CTA after the final failure
Sync Safety Report & Risk Flags
"As a compliance-minded admin, I want a clear report of any risks before I activate sync so that I can remediate issues proactively."
Description

Compiles a consolidated safety report summarizing permission scope verification, schema drift, missing required fields, timezone/locale anomalies, attachment size limits, and data quality checks (spam, empty text, non-supported languages). Provides recommended fixes, auto-fix suggestions where safe (e.g., default timezone), and explicit callouts for residual risk. Exportable as JSON/PDF for audit. Integrates with observability and compliance logging to record the preview’s assumptions and results.

Acceptance Criteria
Read-Only Dry-Run Guarantee
Given Sync Preview is initiated for one or more sources When all API requests are issued Then only GET/HEAD requests are sent; any POST/PUT/PATCH/DELETE are blocked before dispatch and logged as policy_violation And the safety report displays "No data mutation performed" with a link to the audit entry And source tool objects remain unchanged as verified by zero write operations in request logs
Permission Scope Verification & Residual Risk
Given a connection with declared OAuth scopes or API token permissions When the preview validates scope coverage against the minimum required per source and endpoint Then missing or insufficient scopes are listed with scope name, required vs. present, affected endpoints, and estimated records blocked (count and % of sample) And recommended fixes are provided (exact scope strings or role names) And a residual risk callout is shown if activation proceeds without all required scopes And no calls requiring missing scopes are attempted during preview
Schema Drift & Missing Required Fields Detection
Given mappings exist between source fields and the EchoLens schema When the preview inspects a sample of up to 10,000 records Then field deletions, additions, and type changes relative to the mapping are detected and labeled as Breaking or Non-breaking with counts And missing required fields are listed with source, path, expected type, sample record ids, and % of sample impacted And recommended fixes are included (update mapping, backfill, add default) And auto-fix suggestions are only marked Safe when default values do not change semantics; otherwise Unsafe with residual risk callout
Timezone & Locale Anomalies with Safe Auto-Fix Suggestion
Given records contain timestamps and locale/language codes When the preview analyzes formats and values Then anomalies are quantified: % missing timezone, % non-ISO formats, % locales unsupported by EchoLens And if a default timezone is configured at project level, a Safe auto-fix is suggested and simulated, with assumptions recorded And if no safe default is available, a residual risk callout is displayed with steps to resolve
Attachment Size Limits & Truncation Risk Estimation
Given source-specific attachment size limits and platform-configured max_payload_mb When the preview scans attachments in the sample Then counts and total bytes over the limit are reported per source with p95 and max sizes And the report estimates records to be skipped or truncated under current settings And recommendations are provided (exclude attachments, raise limit up to allowed maximum); all actions are simulated only
Data Quality Checks: Spam, Empty Text, Unsupported Languages
Given spam classifier threshold = 0.90, empty text threshold = <3 visible characters, and an allowlist of supported languages When the preview evaluates message bodies Then the report shows counts and percentages for spam, empty, and unsupported language items per source and overall And the expected post-filter volume and change in theme coverage (%) are displayed And recommendations are provided (adjust thresholds, extend language allowlist)
Audit Exports and Observability/Compliance Logging
Given a completed preview run When the user exports the safety report Then JSON (schema version v1) and PDF are generated within 10 seconds for samples up to 10,000 records and are downloadable for 24 hours with SHA-256 checksum And the JSON conforms to the published schema including run_id, timestamp_utc, initiator_user_id, sources, assumptions, risks, recommendations, simulated_actions, and summary metrics And structured telemetry and compliance audit entries are emitted at start and completion with trace_id, run_id, status, counts, and hash of the exported artifacts; payloads contain no PII beyond metadata keys
One-click Activate with Config Snapshot
"As a busy PM, I want to activate using the exact settings I previewed so that there are no surprises when the sync starts."
Description

Enables promotion from preview to live sync with a single action that freezes the exact configuration: sources, filters, deduping rules, redaction, and schedules. Version-controls the configuration, stores an immutable snapshot with checksum, and supports rollback to the previewed state. Presents a diff if changes occurred since the last preview. Integrates with the job scheduler and Jira/Linear sync to carry forward mappings without rework.

Acceptance Criteria
One-click Activate Freezes Exact Preview Configuration
Given a completed Sync Preview with configuration hash H When the user clicks "Activate" from the preview context Then the live sync is created using exactly the previewed sources, filters, deduping rules, redaction settings, and schedule And the resulting active configuration hash equals H And the UI displays an activation confirmation containing snapshot ID and hash
Immutable Config Snapshot with Checksum and Versioning
Given an activation occurs When the system persists the configuration snapshot Then the snapshot is stored as write-once immutable And a monotonically increasing version number is assigned And a SHA-256 checksum is stored and equals the computed checksum of the snapshot payload And retrieving the snapshot by ID returns a payload whose checksum matches the stored checksum
Rollback to Previewed State
Given a live sync at version N created from preview version N-1 When a user with rollback permission selects "Rollback to Preview" Then the active configuration switches to version N-1 And the active configuration hash equals the N-1 snapshot hash And a rollback event is recorded with user, timestamp, from-version, and to-version And the next scheduled run uses the N-1 configuration
Change Diff Between Preview and Activation
Given the configuration has changed since the last completed preview When the user opens the Activate dialog Then a diff view displays added, removed, and modified fields with per-field counts And the diff includes sources, filters, deduping rules, redaction, and schedule changes And the "Activate" action is disabled until the user explicitly confirms the changes And the diff view is downloadable or copyable for audit
Carry Forward Jira/Linear Mappings on Activation
Given the preview established Jira/Linear mappings with identifiers M (projects, issue types, field mappings) When the user activates from that preview Then the active configuration retains the same mapping identifiers M without requiring re-authorization And no duplicate mappings are created in Jira/Linear And the first live sync run uses mappings M for all outbound items
Scheduler Integration and First-Run Behavior
Given the preview includes a schedule S When the user activates at time T Then the first job is scheduled for the next occurrence per S relative to T, unless S specifies "run on activate" And subsequent jobs execute per S without drift greater than one interval And pausing the sync halts scheduled jobs without deleting the active snapshot
Audit and Data Safety Controls on Activation
Given activation is initiated When the system connects to configured source tools Then only read scopes are used and no create/update/delete operations occur in source systems And an audit log entry is recorded with user, timestamp, snapshot ID, checksum, and outcome (success/failure) And on failure, no partial configuration becomes active and the prior state remains effective

Account Unifier

Detects duplicate or parallel environments (sandbox, staging, prod) and guides you to the canonical sources with guardrails to prevent double‑counting. Keeps analytics clean while preserving the option to connect sandboxes for testing.

Requirements

Environment Fingerprinting & Auto-Detection
"As a product manager, I want EchoLens to automatically detect sandbox, staging, and production duplicates so that I can quickly unify accounts and keep analytics accurate."
Description

Automatically identifies duplicate or parallel environments (sandbox, staging, production) by building an environment fingerprint from signals such as domain patterns, API key scopes, metadata fields, traffic volume patterns, headers, and user-provided tags. Computes a confidence score per suspected duplicate group and continuously reevaluates as new data arrives. Surfaces detections via in-app banners and the Settings > Accounts view, with links to review. Integrates at the ingestion and identity resolution layers so that reviews, emails, tickets, and chats are tagged with an environment label without delaying processing. Stores labels in a centralized account graph for downstream analytics, clustering, and syncing without data loss or downtime.

Acceptance Criteria
Multi-signal Environment Fingerprint
Given sample accounts emit events containing domain patterns, API key scopes, metadata fields, headers, and user-provided tags When 1,000 events are processed within 60 seconds Then an environment fingerprint is computed per account using at least 4 distinct signal categories when available And an environment label (sandbox|staging|production|unknown) and confidence score in [0.0, 1.0] are produced for each suspected environment
Duplicate Group Detection and Confidence Thresholding
Given two or more environments with overlapping org/account identifiers and differing domain patterns or API key scopes When the detector processes their events Then they are assigned to a suspected duplicate group with a stable group_id And the group is surfaced only when group confidence >= 0.80 And on a labeled fixture of at least 200 accounts, precision at threshold 0.80 is >= 0.95 and recall >= 0.85
Continuous Re-evaluation on New Data
Given a previously detected group with confidence below the surfacing threshold When 500 additional events arrive that materially change the signal alignment Then the group confidence is recomputed within 5 minutes and the updated confidence is returned by the API and shown in UI And any change to environment label or group membership is versioned with timestamp and reason code
In-App Surfacing: Banners and Settings > Accounts
Given a detected duplicate group with confidence >= 0.80 When an organization admin signs in Then an in-app banner appears within 5 seconds on Dashboard and Ingestion pages summarizing the detection and linking to Settings > Accounts (Review environments) And the Settings > Accounts view lists the group with environment labels, confidence scores, and a View details link And dismissing the banner persists per user and reappears only when new qualifying groups are detected
Non-blocking Tagging at Ingestion and Identity Resolution
Given an incoming review, email, ticket, or chat item When it reaches the ingestion layer Then an environment label is attached before clustering without delaying processing (added p95 latency <= 100 ms at 10k items/min) And if the labeler is temporarily unavailable, the item is accepted with env=unknown and backfilled within 10 minutes without loss
Centralized Account Graph Storage and Downstream Availability
Given a newly assigned environment label or group_id When the record is written to storage Then the account graph reflects the label and group_id within 60 seconds And analytics and clustering queries can filter by environment label and group_id via the public API And Jira/Linear sync payloads include the environment label field without breaking existing integrations
Historical Backfill Without Data Loss or Downtime
Given a workspace with 6 months of historical data When environment detection is enabled Then 100% of historical records are labeled or marked env=unknown within 24 hours And the platform remains available with no ingestion downtime and zero data loss And backfill progress is observable via an admin endpoint or a Settings progress indicator
Canonical Source Selection & Merge Suggestions
"As a CX lead, I want to review and select a canonical source among duplicates so that data flows to the right account without losing history."
Description

Provides a guided review workflow to confirm suspected duplicate groups and select a canonical production source. Displays side-by-side metrics, recent activity, connected integrations, and confidence scores to inform the decision. Applies a mapping that routes new events from non-canonical environments to the canonical account for analytics while preserving their original environment label for filtering. Simulates the impact of the merge before applying, then executes a safe, idempotent unification that updates references in queues, live theme maps, and sync payloads to Jira/Linear. Supports backfill reassignment of historical items with progress tracking and the option to exclude sandbox history from analytics by default.

Acceptance Criteria
Side-by-Side Duplicate Review Panel
Given a suspected duplicate group is opened in Account Unifier When the review panel loads Then it displays two or more environments side-by-side with metrics (last 30 days events, active users, last activity timestamp) and connected integrations per environment And shows a confidence score per pair/group with an explanation tooltip And loads within 2 seconds for groups of up to 5 environments And surfaces a warning badge if any environment is marked sandbox or staging
Canonical Source Selection with Guardrails and Audit
Given the user has org admin permissions and a duplicate group is under review When the user selects a canonical environment and attempts to confirm Then prevent selection of sandbox or staging as canonical unless the user explicitly checks an override control And require typed confirmation of "Set canonical" and show the count of affected environments And record an audit log entry with user, timestamp, canonical ID, affected IDs, and previous state And disable confirmation if no environment is selected or the selection is unchanged And show a success banner upon confirmation
Pre-Merge Impact Simulation
Given a canonical and one or more non-canonical environments are selected When the user runs "Simulate impact" Then the system computes and displays projected analytics changes (total events, unique users, top 5 themes, queue ranks) for last 30 days and all-time And highlights items to be remapped in queues, live theme map, and Jira/Linear sync payloads with counts by type And completes within 10 seconds for datasets up to 1,000,000 events And provides a downloadable CSV summary of remap counts by entity type And disables "Apply merge" until a simulation has been run on the current selection
Idempotent Merge Execution and Reference Updates
Given a simulation has been completed within the last 30 minutes and the selection is unchanged When the user clicks "Apply merge" Then the system performs an idempotency check using a merge fingerprint and refuses duplicate executions And reroutes all future events from non-canonical environments to the canonical account for analytics while preserving the original environment label for filtering And updates references in queues, the live theme map, and outbound sync payloads (Jira/Linear) within 60 seconds And emits events/webhooks for downstream consumers with the mapping details And retries transient failures up to 3 times with exponential backoff and surfaces a failure summary with retriable actions
Historical Backfill Reassignment with Progress Tracking
Given the user opts to backfill historical items When backfill starts Then historical items (events, feedback, tickets) are reassigned to the canonical account according to mapping rules while preserving the original environment label And a progress UI shows percent complete, items processed per minute, ETA, and provides pause/resume controls And the user can exclude sandbox/staging history by default with an override toggle And the backfill processes up to 10,000,000 items without duplication and resumes safely after transient failures And a completion report summarizes moved counts by type and any exceptions
Post-Merge Routing and Data Integrity
Given a merge has completed When new events arrive from a previously non-canonical environment Then analytics attribute them to the canonical account while retaining an environment=original label for filtering And no duplicate user or event counts are produced when the same user acts across environments And the system exposes a queryable mapping table (source_env_id → canonical_id) available in API and UI filters And daily integrity checks validate that mapped counts equal the sum of source environments within 0.1% tolerance and alert on drift
Merge Suggestions Ranking and Dismissal
Given the Account Unifier has suggested duplicate groups When the suggestions list is viewed Then groups are ranked by confidence score and recent activity impact And each suggestion displays top drivers (e.g., domain match, integration overlap, user overlap) with weights And the user can accept, snooze, or dismiss a suggestion with required reason codes And dismissed suggestions do not reappear unless confidence or activity changes exceed a defined threshold and all actions are logged And accepting a suggestion opens the review panel pre-populated with the group selection
Double-Counting Guardrails & Deduplication Engine
"As a data analyst, I want guardrails that prevent double-counting across environments so that trend and impact rankings remain trustworthy."
Description

Implements cross-environment deduplication to prevent inflated counts when the same issue appears in sandbox/staging and production. Generates deterministic deduplication keys per item using a combination of normalized content hashes, timestamps, conversation/thread identifiers, customer/account IDs, and environment labels. Applies deduplication at ingestion and aggregates at query time so that cluster volumes, confidence scores, and ranked queues reflect unique real-world signals. Excludes sandbox/staging from analytics by default with workspace-level overrides, and visually indicates when counts include test environments. Ensures idempotency and concurrency safety across data sources to avoid race conditions and partial merges.

Acceptance Criteria
Deterministic Deduplication Key Generation
Given an item with content, timestamp, conversation_id, customer_id, account_id, and environment_label When the deduplication engine generates a dedup_key Then the dedup_key is identical across 1,000 repeated generations and across services using the same algorithm Given two items whose contents differ only by case, whitespace, and common punctuation When normalized content hashing is applied Then the dedup_key is identical Given two items with different customer_id or conversation_id values When dedup_keys are generated Then the dedup_keys differ Given the same algorithm version is configured When dedup_keys are regenerated after 30 days Then the dedup_keys are identical to the originals
Cross-Environment Deduplication at Ingestion
Given two items from sandbox and production that produce the same dedup_key When both are ingested in any order Then exactly one canonical record is persisted with canonical_environment=production and the other is stored as a duplicate referencing the canonical record Given the same pair is ingested again due to retries When ingestion reprocesses them Then the operation is idempotent and no additional canonical or duplicate records are created Given 100 parallel ingestion events for the same pair When processing completes Then exactly one canonical record exists and no race-condition or partial-merge errors are logged
Query-Time Aggregation Reflects Unique Signals
Given a cluster with 10 production items and 3 sandbox items sharing dedup_keys with those production items When the cluster volume is displayed Then the volume equals 10 Given a cluster with 2 distinct production dedup_keys and each has 2 duplicates in staging When the cluster volume and confidence score are computed Then the volume equals 2 and the confidence score is unchanged when staging duplicates are added or removed Given the ranked queue is computed When duplicates exist across environments Then rankings are based on deduplicated volumes and do not increase due to test-environment duplicates
Test Environments Exclusion by Default with Workspace Override
Given default workspace settings When analytics views load Then sandbox and staging items are excluded from volumes, confidence scores, and rankings Given an admin enables the Include test environments override at the workspace level When analytics are refreshed Then sandbox and staging items are included and all affected metrics update within 60 seconds Given test environments are included When analytics views load Then a visible badge labeled Includes test environments appears on clusters and queues; when excluded, a badge labeled Test environments excluded is shown Given the override is changed When the user returns after sign-out/sign-in Then the override state persists for the workspace
Idempotency and Concurrency Safety Across Data Sources
Given the same external item arrives via two connectors with the same conversation_id and customer/account When both are ingested concurrently Then one canonical record is created and the second is linked as a duplicate to the canonical without partial merges Given a crash occurs after the duplicate is detected but before persistence completes When the ingestion is retried Then the final state is a single canonical record with duplicates linked and no double-counting in analytics Given a batch backfill replays the last 24 hours of events When processing completes Then no additional canonicals are created for items already ingested and analytics totals remain unchanged
Dedup Quality and Collision Thresholds
Given an annotated validation set of at least 5,000 cross-environment item pairs When the deduplication model is evaluated Then duplicate detection precision is >= 0.98 and recall is >= 0.95 Given the same validation set When dedup_keys are generated for all non-duplicate pairs Then the key collision rate among non-duplicates is <= 0.1% Given a rolling 7-day production monitoring window When measuring post-dedup false-positive rate via sampled audits Then the estimated false-positive rate is <= 0.5%
Sandbox Connect in Test Mode
"As a QA engineer, I want to connect sandboxes in a test mode so that I can validate integrations without polluting production analytics."
Description

Allows teams to connect sandbox and staging integrations in a dedicated test mode that isolates their data from production analytics. Tags all test-mode events and routes them to a separate workspace view, with optional time-to-live and one-click purge to keep indexes clean. Provides a toggle to include or exclude test data per view and an always-on watermark to prevent confusion. Supports end-to-end validation by enabling creation of test tickets and syncs to Jira/Linear flagged as test so they do not affect production reporting or SLAs. Enables context switching between test and production for QA without reconfiguring connectors.

Acceptance Criteria
Connect Sandbox in Test Mode and Isolate Data
Given a sandbox or staging integration is connected in Test Mode When events are ingested from this integration Then each event is tagged environment = "test" And the events appear only in the Test workspace view And the events do not appear in production analytics, charts, or ranked queues And after ingesting 100 test events, production totals and KPIs remain unchanged
Per-View Toggle to Include/Exclude Test Data
Given I am viewing any analytics view (e.g., Theme Map, Ranked Queue) with the default of excluding test data When I toggle Include Test Data to ON for this view Then charts, tables, and queues recompute to include test-mode events And a visible indicator "Test Data Included" is shown on the view When I toggle Include Test Data to OFF Then the view recomputes to exclude test-mode events and removes the indicator And the toggle state persists only for the current view and is restored on page refresh for that view And the toggle state does not affect other views or other users
Always-On Test Watermark
Given I am operating in Test Mode or have included test data in a view Then an always-on watermark labeled "TEST MODE" is visible on all relevant pages and exports in that context And the watermark cannot be dismissed or hidden by user settings And the watermark is not shown when viewing production-only data And screenshots and exported reports from test views include the watermark
Time-to-Live and One-Click Purge for Test Data
Given a Test Data TTL of 24 hours is configured When 24 hours elapse from ingestion time Then all expired test events and their derived indexes are automatically removed And production analytics remain unaffected by the removal When I click Purge Now in Test Data settings Then all existing test events and derived artifacts up to the current time are deleted within 2 minutes And the Test workspace view reflects zero remaining events after purge And a purge audit log entry is recorded with actor, timestamp, and counts removed
Context Switch Between Test and Production Without Reconfiguring Connectors
Given both production and sandbox connectors are authorized When I switch context from Production to Test using the context switcher Then no connector reconfiguration or re-authentication is required And subsequent ingested events from the sandbox route to Test workspace When I switch context back to Production Then sandbox connectors remain authorized and unchanged And production events continue routing to production analytics with no side effects on connector settings
Test Tickets and Syncs to Jira/Linear Flagged and Isolated
Given I create a ticket from a test-mode queue and sync it to Jira or Linear Then the created issue includes a clear test indicator (e.g., label "echolens-test" and title prefix "[TEST]") And the sync record in EchoLens is stored with test = true And the issue is excluded from production SLA reports and KPIs within EchoLens And no production alerts or escalation rules are triggered by test issues And outbound webhooks/callbacks for the issue include a test flag
Guardrails Prevent Double-Counting Across Environments
Given sandbox and production integrations have overlapping identifiers When identical event IDs are received in both environments Then only the production event contributes to production analytics and counts And the test event is visible only in the Test workspace and is excluded from production KPIs And a deduplication log entry is recorded with reason = "test_duplicate" When a user attempts to merge a test account into a production account Then a blocking warning is displayed and the merge is prevented unless the test account is converted to production
Unification Audit Trail & Rollback
"As an administrator, I want a full audit trail with rollback so that I can trace and reverse unification changes if needed."
Description

Captures a complete, immutable audit log for detection decisions, label overrides, and merge operations including actor, timestamp, rationale, and before/after diffs. Exposes a searchable history with export capability for compliance. Supports one-click rollback that restores prior mappings and reprocesses affected events, queues, and theme map aggregates without data loss. Sends notifications to owners when unification changes are applied or reversed and safeguards against conflicting edits with optimistic locking.

Acceptance Criteria
Audit Log Fields Recorded for Unification Actions
Given a user commits a detection decision, label override, or merge operation When the action is saved Then an audit record is written containing actor ID, actor role, timestamp (UTC ISO 8601), action type, affected entity IDs, rationale text, before-state snapshot, after-state snapshot, and a computed diff summary Given an audit record is created Then it is associated to a monotonically increasing version for the affected mapping and a unique immutable record ID Given a user attempts to save an override or merge without a rationale When they click Save Then the action is blocked and a validation error explains that rationale is required
Immutable and Tamper-Evident Audit Storage
Given an audit record exists When any user or process attempts to edit or delete it via UI or API Then the operation is rejected with HTTP 403 (or UI error), and the attempt is itself logged without altering the original record Given the audit trail for a mapping Then records are write-once and linked by a cryptographic hash chain (prev-hash, record-hash) such that any modification is detectable during verification Given system startup or a scheduled job runs daily When audit integrity verification executes Then 100% of chains validate or the system raises an alert identifying the first invalid record
Searchable Audit History with Fine-Grained Filters
Given an auditor opens the audit history view When they filter by actor, action type, entity ID, time range, and rationale contains substring Then the result set reflects all filters accurately and returns within 2 seconds for datasets up to 100k records Given results exceed one page When the user paginates Then cursor-based pagination is used with a default page size of 50 and a configurable maximum of 200 Given a user lacks access to an account When they search the audit history Then records for that account are excluded from results
Export Audit History for Compliance
Given an authorized user applies filters and selects Export When they choose CSV or JSON Then the export contains exactly the filtered records and fields (including record ID, version, hashes) and preserves timestamps in UTC Given an export is requested for up to 100k records Then the download link is available within 10 seconds and the export completes within 2 minutes Given an export exceeds 1 GB Then a secure, access-controlled link is generated that expires in 24 hours Given an export is generated Then an audit record is created capturing requester, timestamp, filter summary, format, and record count
One-Click Rollback of Unification Change
Given a user selects an audit entry representing a unification change When they click Rollback and confirm Then the prior mapping state is restored exactly and an audit record is created noting the rollback action and rationale Given rollback completes Then all affected events are reprocessed and theme map aggregates and ranked queues are updated within 5 minutes at P95 without data loss Given dependent unification changes exist after the target change When attempting rollback Then the system presents the dependency chain and requires inclusion of dependent changes or cancellation; partial rollback is prevented Given a rollback is triggered multiple times for the same target Then the operation is idempotent and subsequent attempts make no further changes
Owner Notifications on Apply and Rollback
Given a unification change is applied or rolled back Then owner notifications are sent via in-app and any opted-in channels (e.g., email) within 60 seconds, including actor, action type, rationale, affected entities count, and a deep link to the audit record Given a notification delivery failure occurs Then the system retries with exponential backoff for up to 24 hours and logs the failure status Given multiple notifications for the same event and channel are enqueued Then duplicates are de-duplicated so recipients receive at most one per channel
Optimistic Locking Guards Against Conflicting Edits
Given two users load the same mapping version When User A saves changes first Then User B's subsequent save with a stale version token is rejected with HTTP 409 (or UI conflict message) and no partial updates are committed Given any unification mutation request (create, override, merge, rollback) Then a valid version token/ETag is required; missing or mismatched tokens are rejected Given a conflict is detected Then the UI offers to reload the latest state and displays a diff of remote vs. local changes; a conflict attempt is recorded in the audit log
Role-Based Access Controls for Unification Actions
"As a workspace admin, I want role-based controls on unification actions so that only authorized users can modify environment mappings."
Description

Restricts sensitive unification capabilities to authorized roles using the existing SSO-backed RBAC model. Defines granular permissions for reviewing detections, approving merges, overriding environment labels, toggling test mode, and performing rollbacks. Enforces permissions across UI and API with comprehensive error messaging and telemetry on attempts. Provides a permissions matrix in settings and supports least-privilege defaults for new workspaces.

Acceptance Criteria
Viewer Blocked from Unification Actions
Given an authenticated user with role Viewer and no unification permissions When they attempt any of the following via the UI: review detections, approve merge, override environment label, toggle test mode, or perform rollback Then the corresponding controls are hidden or disabled with a tooltip indicating the missing permission And if the user invokes any corresponding API endpoint directly, the response is 403 with error.code specific to the action (RBAC_403_UNIFY_REVIEW | RBAC_403_UNIFY_APPROVE | RBAC_403_OVERRIDE_ENV_LABEL | RBAC_403_TOGGLE_TEST_MODE | RBAC_403_ROLLBACK) And no unification state changes occur And a telemetry event UNIFY_PERMISSION_DENIED is recorded for each attempt with fields {user_id, role, action, resource_id, channel: ui|api, trace_id, timestamp}
Approver Can Approve Merges via UI and API
Given a user authenticated via SSO with role Unification Approver and permission unify.approve_merge = true When they approve a detected duplicate merge from the UI Then the action succeeds, a success banner appears, and the API returns 200 with body {merge_id, status:"approved"} And the merged accounts resolve to the designated canonical account And an audit/telemetry event UNIFY_MERGE_APPROVED is recorded with fields {actor_id, merge_id, canonical_account_id, source: "ui", trace_id} Given the same user When they call POST /unifier/merges/{merge_id}/approve via API Then the response is 200 with status "approved" and the same audit/telemetry event is recorded with source: "api"
Reviewer Can View Detections Without Dangerous Actions
Given a user with permission unify.review = true and all other unification permissions = false When they open the Account Unifier detections list and detail views Then detections, environment groupings, and confidence scores are visible (200 OK) And controls for approve merge, override environment label, toggle test mode, and rollback are not rendered And attempts to invoke write endpoints return 403 with the appropriate RBAC error.code And a telemetry event UNIFY_DETECTIONS_VIEWED is recorded with fields {user_id, filters, count, timestamp}
Environment Label Override Restricted and Validated
Given a user without permission unify.override_env_label When they attempt to change an environment label (e.g., sandbox → prod) Then the UI prevents submission and any API attempt returns 403 with error.code = RBAC_403_OVERRIDE_ENV_LABEL And no label changes are persisted Given a user with permission unify.override_env_label = true When they change an environment label via the UI Then the API returns 200 and the new label must be one of ["sandbox","staging","prod"], case-insensitive And an audit/telemetry event UNIFY_ENV_LABEL_OVERRIDDEN is recorded with fields {actor_id, account_id, old_label, new_label, reason, trace_id}
Toggle Test Mode Requires Permission and Is Non-Persistent
Given a user with permission unify.toggle_test_mode = false When they attempt to enable Test Mode for the workspace Then the action is blocked with 403 (error.code = RBAC_403_TOGGLE_TEST_MODE) and no mode change occurs Given a user with permission unify.toggle_test_mode = true When they enable Test Mode Then the workspace displays a visible "Test Mode" indicator And subsequent unification actions are executed as dry-run (no canonical state persisted) and API responses include dry_run=true And a telemetry event UNIFY_TEST_MODE_TOGGLED is recorded with fields {actor_id, workspace_id, enabled:true, trace_id}
Rollback Restricted and Fully Reverts Merge
Given a user with permission unify.rollback = false When they attempt to rollback a merge Then the API returns 403 with error.code = RBAC_403_ROLLBACK and no changes occur Given a user with permission unify.rollback = true When they rollback merge {merge_id} Then the pre-merge state is restored for all affected accounts/environments And a telemetry event UNIFY_ROLLBACK_EXECUTED is recorded with fields {actor_id, merge_id, reason, trace_id} And subsequent reads reflect the restored state in both UI and API
Permissions Matrix Visible and Least-Privilege Defaults Applied
Given an Admin navigates to Settings → Permissions Matrix When the page loads Then the matrix lists the unification permissions with readable labels: unify.review, unify.approve_merge, unify.override_env_label, unify.toggle_test_mode, unify.rollback And current role assignments are displayed and can be edited by Admins And changes saved in the matrix are enforced on subsequent UI actions and API calls without requiring user re-login Given a newly created workspace with default roles When a Member signs in Then the Member has none of the unification permissions by default (verified by 403 on protected actions and visible matrix state) And an Admin can grant individual permissions to specific roles to uphold least-privilege

Decay Tuner

Fine‑tune the Theme Health Score’s half‑life by theme type, segment, or channel to match real‑world urgency. Preview how different decay settings affect the heatbar and trigger windows before applying. Keeps signals crisp for fast‑moving issues while letting slow‑burn problems stay visible long enough to act.

Requirements

Segmented Decay Rules Engine
"As a product manager, I want to set different decay half-lives by theme type, segment, and channel so that urgent signals fade quickly while slow-burn issues stay visible long enough to act."
Description

Configurable decay half-life per theme type, customer segment (e.g., plan/tier, ARR band, region), and source channel (reviews, email, tickets, chat) with a clear precedence model where specific rules override general defaults. Provides a global fallback, presets for common scenarios (e.g., outages/regressions vs. usability), and unit selection in hours or days. Enforces validation with min/max bounds and conflict detection. Integrates into the scoring pipeline so Theme Health Scores, heatbar intensity, and trigger windows consistently reflect the active decay rules.

Acceptance Criteria
Most Specific Rule Precedence Resolution
Given a global default decay rule exists And a theme-only rule exists for theme type "Outage" And a theme+segment+channel rule exists for theme "Outage", segment "Enterprise", channel "Tickets" When scoring a theme of type "Outage" from segment "Enterprise" with source "Tickets" Then the decay half-life resolved is from the theme+segment+channel rule When scoring a theme of type "Outage" from segment "SMB" with source "Email" Then the decay half-life resolved is from the theme-only rule When scoring a theme of type "Usability" from any segment with source "Reviews" and no matching specific rules Then the decay half-life resolved is from the global default rule And the precedence order applied is: theme+segment+channel > theme+segment > theme+channel > theme only > segment+channel > segment only > channel only > global default And the resolved half-life is used consistently for Theme Health Score, heatbar intensity, and trigger windows
Half-Life Validation and Unit Selection
Given system bounds are configured as minHalfLife = 1 hour and maxHalfLife = 90 days When a user selects unit "hours" and inputs 0.5 Then the form shows an inline error stating the allowed range is 1 hour to 90 days and disables Save When a user selects unit "days" and inputs 91 Then the form shows the same inline error and disables Save When a user inputs a valid value within bounds (e.g., 24 hours) Then Save becomes enabled and the value persists with the chosen unit When a user switches units between hours and days before saving Then the numeric value converts correctly and re-validates against bounds When a non-numeric value is entered Then the form rejects the input and shows a validation error
Conflict Detection for Overlapping Rules
Given an existing rule for theme "Usability" and channel "Email" When a user attempts to create another rule with the same condition keys (theme "Usability", channel "Email") Then the system blocks save and displays a conflict error listing the conflicting rule identifier with a link to edit it When a user creates a more specific rule for theme "Usability", channel "Email", segment "Enterprise" Then the system allows creation and indicates in preview that it overrides a less specific rule per precedence When a user attempts to duplicate any rule with identical scope Then save is blocked until the conflict is resolved by editing or disabling the existing rule
Preset Application and Impact Preview
Given presets for "Outage/Regression" and "Usability Improvements" are available When a user applies the "Outage/Regression" preset to selected scopes (channels Tickets and Chat; segments Enterprise and SMB; relevant theme types) Then decay values populate per preset for those scopes without modifying unrelated rules And the Preview mode displays predicted changes including heatbar intensity deltas, trigger window shifts, and count of affected themes And no Theme Health Scores or live data are altered during Preview When the user confirms Apply Then the rules are saved and marked active for the next scoring run
Scoring Pipeline Consistency and Live Map Update
Given a baseline of Theme Health Scores and an upcoming scoring cycle When new decay rules are applied Then the scoring engine uses the resolved half-life per precedence for each theme And the Theme Health Scores, heatbar intensity, trigger windows, and live theme map update in the next scoring cycle to reflect the new decay And for sampled themes impacted by shorter half-life, post-apply scores decrease relative to baseline and match Preview-predicted deltas within 2 percentage points
Global Fallback Enforcement
Given exactly one global fallback decay rule must exist at all times When no specific rule matches a theme's type, segment, or channel Then the global fallback half-life is applied When an admin edits the global fallback within valid bounds Then all unmatched themes use the new value after the next scoring cycle And deletion of the global fallback is disallowed; only editing is permitted
Live Impact Preview Simulator
"As a CX lead, I want to preview the impact of decay changes on scores and the heatbar before saving so that I can tune urgency without destabilizing the triage queue."
Description

Real-time, sandboxed preview that simulates proposed decay settings on historical data to show expected changes to Theme Health Score curves, heatbar intensity, and trigger windows before applying. Supports date range selection, sampling vs. full recalculation for performance, deltas and rank-shift highlights for top themes, and uncertainty bands influenced by confidence scores. No writes to production until published.

Acceptance Criteria
Real-time Preview on Historical Data
Given an Editor or Admin opens Decay Tuner > Live Impact Preview When they adjust decay half-life by Theme Type and select a date range within the past 12 months with Mode=Sampled Then the Theme Health Score curve, heatbar intensity, and trigger window overlays render within 2 seconds in 95% of attempts and display a progress indicator during computation And baseline vs preview toggle overlays both series and shows per-point delta (absolute and %) rounded to one decimal place And the preview reflects only historical data within the selected range
Date Range Selection & Validation
Given the user opens the date range picker When they pick Start and End within account data bounds Then Apply is enabled and the selection is persisted across mode toggles and parameter edits And the allowed range is 7 to 365 days; selecting less than 7 days or more than 365 days disables Apply and shows an inline error And boundaries are inclusive and interpreted in the workspace timezone And empty or null selection keeps prior valid range and shows helper text
Sampling vs Full Recalculation Modes
Given the preview mode toggle is visible When Mode=Sampled (default 10% stratified by theme and channel) is selected Then the preview renders within 2 seconds in 95% of attempts and displays an Estimated Error badge And when Mode=Full Recalc is selected Then results render within 10 seconds for ranges <= 12 months or show an ETA banner; if processing exceeds 60 seconds, a retry CTA is shown without losing the current parameters And for the same parameters, sampled aggregate Theme Health Scores differ from full by <= 5% relative error for the top 20 themes
Delta and Rank-Shift Highlights for Top Themes
Given the preview completes When viewing the Top Themes panel Then the top 20 themes are listed by previewed rank, each showing health score delta (absolute and %) and rank shift indicator with up/down arrows and magnitude And a Rank Shift filter allows sorting by Largest Increase, Largest Decrease, Biggest Rank Up, Biggest Rank Down And any theme with |rank shift| >= 3 is highlighted with a pill badge And clicking a theme focuses its curve and heatbar and scrolls to its trigger windows
Uncertainty Bands Influenced by Confidence Scores
Given the Uncertainty Bands toggle is ON When a theme’s confidence score increases by at least 0.2 with all other parameters unchanged Then the visual band width around its health score curve decreases monotonically and the tooltip shows numeric lower/upper bounds aligned with the current confidence level And themes with confidence >= 0.9 show bands no wider than ±5% of the point estimate; themes with confidence <= 0.5 show bands of at least ±20% And turning the toggle OFF hides all bands without altering underlying values
No Writes to Production Until Publish
Given the user is in preview mode When adjusting any decay parameter, date range, or mode Then no writes occur to production decay settings, theme scores, or trigger configurations and no other users see the changes And network calls for preview use read-only endpoints; attempts to persist are blocked until Publish is confirmed And upon clicking Publish, a confirmation modal shows a diff summary (parameters changed, affected themes count, expected rank shifts); only after confirmation are changes committed and audit-logged And canceling or navigating away discards the preview without side effects
Trigger Window Preview Parity
Given trigger rules are defined (e.g., 1-day surge, 7-day sustained, 30-day regression) When previewing with new decay settings over a selected range Then the preview overlays highlight the windows that would have triggered and display counts per rule And cross-checking the same parameters on a staging run of the rules engine yields a trigger count within ±1 per rule for the selected range And triggers starting or ending exactly at range boundaries are labeled with a boundary icon
Versioning, Rollout, and Rollback
"As an admin, I want versioned, safe rollout of decay settings so that I can experiment and revert quickly if unintended effects occur."
Description

Full lifecycle management for decay configurations including drafts, scheduled activation windows, staged rollouts by percentage or segment, and instant rollback. Displays diffs between versions, summarizes affected metrics, and optionally requires approvals before publish. Maintains a complete change history for traceability.

Acceptance Criteria
Draft Creation and Isolation
Given a user with Config Admin permissions creates a new decay configuration version When they save it as Draft Then the version is persisted with status Draft and is not applied to any live scoring or visualizations Given at least one Live version exists When the Draft exists Then all non-preview endpoints continue using the current Live version with 0% of traffic assigned to the Draft When the Draft is deleted Then no change occurs to the Live version and an audit entry is recorded
Scheduled Activation Window
Given a Draft version with Start and End timestamps defined in UTC When the schedule is saved Then the version status becomes Scheduled and validation blocks Start < now or End <= Start with an error When the system time reaches Start Then the version becomes Live within 60 seconds and remains Live until End When End is reached Then the prior Live version is restored within 60 seconds and a history entry is created Given another version has an overlapping activation window When saving the schedule Then the system blocks the save and displays Overlapping activation windows not allowed
Staged Rollout by Percentage and Segment
Given a rollout plan with steps 10% → 50% → 100% targeting segment Beta Customers When publish is initiated and required approvals are satisfied Then at each step start the system assigns the version to a random, deterministic 10%/50%/100% of eligible users in Beta Customers And assignment stickiness is maintained per user for at least 30 days or until rollback/completion And observed coverage over a 15-minute window is within ±2% of target for eligible populations > 5,000, or within ±5% otherwise When Pause is clicked during any step Then no additional users are assigned and current assignments remain sticky When Resume is clicked Then rollout proceeds to the next scheduled step
Instant Rollback to Previous Version
Given version v2 is Live and v1 is the prior stable version When an operator triggers Rollback to v1 Then within 60 seconds all new scoring/evaluation requests use v1 And all scheduled rollout steps for v2 are cancelled And an audit entry records actor, timestamp, target version, and rollback reason And the UI confirms success or surfaces an actionable error with retry if rollback fails
Version Diff and Impact Summary Display
Given two versions v1 and v2 are selected for comparison When the Diff view is opened Then the UI lists all changed parameters (half-life values, theme-type rules, segment/channel overrides, schedules, rollout plans) with before/after values and numeric deltas And added and removed items are explicitly identified And a computed summary shows number of themes affected, % of recent items (last 30 days) impacted, and projected heatbar change for the top 10 themes And a link allows previewing the impact in the theme map without persisting changes
Approval Workflow Before Publish
Given the workspace policy requires N approvers from roles Owner or Admin When a user submits a version for publish Then the version status becomes Pending Approval, eligible approvers are notified, and the Publish action is disabled When N distinct eligible users approve Then the Publish action is enabled and approvals are recorded with timestamps and optional comments When any edit is made to the pending version Then prior approvals are invalidated and approvers are notified When an approver rejects Then a rejection reason is required and the submitter is notified
Immutable Change History and Traceability
Given any configuration lifecycle event occurs (create, edit, submit, approve, publish, schedule change, rollout step change, pause/resume, rollback) When the History view is opened Then each event entry shows actor, UTC timestamp, version IDs, event type, diff snapshot, approval records, rollout percentages/segments, and optional comment And entries are immutable and exportable to CSV and JSON And the system can reconstruct and display the effective configuration at any selected past timestamp And history is searchable and filterable by actor, event type, version, and date range
Historical Recompute & Backfill Pipeline
"As a product manager, I want historical scores to be recomputed after changing decay so that analytics and rankings remain consistent across time."
Description

Background processing to recompute Theme Health Scores, heatbars, and trigger windows after decay changes across a configurable lookback period. Uses incremental, idempotent batches with throttling, prioritizes active themes, and updates derived artifacts like the ranked queue. Triggers downstream syncs (e.g., Jira/Linear) when rank thresholds are crossed and emits webhooks on completion or failure.

Acceptance Criteria
Recompute after Decay Change across Configurable Lookback
Given a user saves new decay settings for one or more theme dimensions with a lookback period L days When the backfill pipeline is triggered Then Theme Health Scores, heatbars, and trigger windows are recomputed for all themes with events within the last L days And no data older than L days is modified And a job record with job_id, settings_hash, and lookback_days=L is created and queued within 2 minutes of the settings save And each recomputed artifact stores a recompute_version referencing the job_id and settings_hash
Incremental, Idempotent Batching with Throttling
Given the recompute job processes in batches under a configured throttle When a batch is retried due to failure Then outputs and side-effects for that batch are not duplicated (idempotent writes) And processing resumes from the last committed checkpoint after a restart And the job exposes batch_count, processed_count, and remaining_count metrics that update monotonically in the correct direction And effective throughput never exceeds the configured throttle limit
Active Themes Prioritization
Given a mixed backlog of active and inactive themes per the product-configured activity window When the job runs Then active themes are scheduled ahead of inactive themes And at least 80% of active themes are completed before more than 20% of inactive themes when both classes exist And ties within a class are broken deterministically
Derived Artifacts Update and Rank Threshold Syncs
Given recompute results in updated theme ranks When a theme crosses a configured rank threshold upward Then a create/update is synced to Jira/Linear exactly once per theme per destination with references to the job_id And when a theme drops below the threshold Then close/comment or update labels per sync configuration without creating duplicates And the ranked queue reflects the new ordering atomically after each theme’s recompute commit And no syncs are sent for themes whose rank did not cross a threshold
Concurrent Decay Changes During Running Backfill
Given a backfill job J1 is running and a user saves new decay settings resulting in job J2 When both jobs exist Then J1 stops before committing further writes and J2 becomes the only job permitted to write final artifacts And any partial artifacts from J1 are rolled back or overwritten by J2 so that the final state matches a clean J2-only execution And J2 is queued within 2 minutes of the new settings save and starts within the global backfill concurrency limits
Completion, Failure, and Webhook Emission
Given a backfill job completes successfully When the final batch commits Then a "backfill.completed" webhook is emitted with job_id, status=success, settings_hash, lookback_days, counts {processed, succeeded, failed, skipped}, start_at, end_at Given a backfill job fails irrecoverably When the failure is recorded Then a "backfill.failed" webhook is emitted with job_id, status=failure, settings_hash, error_code, error_message, failed_batch_id, and counts to date And webhook delivery follows the platform’s retry policy and includes a unique event_id for consumer deduplication
Decay Settings UX Editor
"As a product manager, I want an intuitive editor to configure decay across different dimensions so that I can tune health scoring quickly without engineering help."
Description

An accessible, guided editor to create and manage decay rules with filters for theme type, segment, and channel, numeric inputs and sliders for half-life, and inline mini-previews (sparklines, heatbar chips). Provides preset templates, validation and conflict warnings, drafts with comments, and contextual help explaining decay behavior and best practices. Optimized for quick keyboard-driven workflows.

Acceptance Criteria
Create and Save Decay Rule with Scoped Filters
Given I am in the Decay Settings UX Editor on EchoLens When I select one or more Theme Types, Segments, and Channels as the rule scope Then only those selections are persisted as the rule’s targeting And the Half-life can be set via numeric input or slider and both remain in sync And the allowed half-life range is 1 hour to 180 days, with steps of 1 hour up to 7 days and 1 day thereafter And values outside the allowed range show inline error, prevent Apply, and announce the error to screen readers When I click Save Draft Then the rule is saved with status "Draft" and appears in the rules list with my scope and half-life values
Real-time Inline Preview of Decay Effects
Given a rule scope is selected and preview data is available for that scope When I adjust the half-life by slider or numeric input Then the sparkline and heatbar chip mini-previews update within 200 ms of input change And the preview displays a 30-day window with computed trigger windows based on the current half-life And if preview data is still loading, a skeleton state is shown and Apply is disabled When I Apply the rule Then the first post-apply refresh of the same scope matches the previewed trigger windows within a tolerance of 5% for timing thresholds
Validation and Conflict Warnings for Overlapping Rules
Given I create or edit a rule whose scope overlaps an active rule (same Theme Type/Segment/Channel intersection) When I attempt to Apply Then a conflict banner appears listing the conflicting rules and the conflicting dimensions And Apply is disabled until I either set a higher/lower priority, narrow the scope, or disable the conflicting rule And selecting a resolution immediately recalculates conflicts and removes the banner once resolved And out-of-range inputs and empty required fields show inline validation and are announced via aria-live
Drafts, Comments, and Safe Apply Flow
Given I am editing a rule When I pause typing for 3 seconds or manually click Save Draft Then changes are autosaved as a new draft version with timestamp and author And I can open a comments panel to add, edit, delete, and resolve comments, with @mentions and timestamps When I click Apply Then I see a confirmation dialog summarizing scope and half-life changes and I must confirm to publish And Cancel in the editor reverts unsaved edits to the last saved state (draft or applied)
Preset Templates Populate Settings
Given I open the Templates menu in the editor When I choose Fast-moving Then half-life defaults to 12 hours and scope filters remain unchanged When I choose Balanced Then half-life defaults to 3 days and scope filters remain unchanged When I choose Slow-burn Then half-life defaults to 14 days and scope filters remain unchanged And after selecting any template, I can further adjust values before saving or applying
Keyboard-Driven Workflow and Accessibility Compliance
Given I use only the keyboard When I press N Then focus moves to a new rule form with the first field focused When I press Ctrl/Cmd+S Then the current rule saves as Draft When I press Ctrl/Cmd+Enter Then Apply is triggered if validation passes And all interactive elements are reachable via Tab/Shift+Tab in a logical order, have visible focus indicators, and expose names/roles via ARIA And sliders are operable via Arrow/Page keys with the defined step values and announce current value and units And the editor passes automated accessibility checks (no serious/critical axe-core issues, color contrast >= 4.5:1)
Contextual Help Explains Decay Behavior and Best Practices
Given I am in the editor When I press ? or click the help icon Then a contextual help panel opens explaining half-life, decay impact on heatbars/trigger windows, and best-practice guidance with two concrete examples And the panel includes a Learn more link opening documentation in a new tab And the help text reads at Flesch–Kincaid grade level <= 8 and is dismissible; the dismissal state persists for the session And tooltips are available on Half-life, Preview, and Conflict areas with concise definitions
Role-Based Access & Audit Trail
"As a workspace admin, I want to restrict and audit changes to decay settings so that governance and compliance requirements are met."
Description

Granular permissions controlling who can view, edit, approve, and publish decay settings, aligned to workspace roles and SSO groups. Comprehensive audit logs for previews, edits, publishes, and rollbacks with timestamps and user IDs, exportable for compliance reviews.

Acceptance Criteria
View Access Enforcement for Decay Tuner Settings
- Given a user with the View Decay Settings permission, when they open the Decay Tuner UI, then they can see current half-life values and preview controls but cannot see or activate edit/approve/publish controls. - Given a user without the View Decay Settings permission, when they navigate to the Decay Tuner route or call GET /decay-settings, then the system returns 403 (or 404 per security policy) and no configuration data is leaked in the response. - Given an authorized viewer, when they attempt to call PATCH/POST/DELETE endpoints for decay settings, then the system returns 403 and no change is applied. - Given any access (authorized or denied), when the UI or API for viewing decay settings is invoked, then an access event with userId, timestamp (UTC), route, and outcome (allowed/denied) is recorded in the audit log.
Draft Edit Permissions and Approval Workflow
- Given a user with Edit Decay Settings permission, when they modify half-life by theme type/segment/channel, then a new Draft version is created and saved with a unique version ID and required change reason. - Given a user with Edit Decay Settings permission but without Approve/Publish permission, when they attempt to publish a draft, then the publish action is disabled in the UI and returns 403 via API. - Given a user with Approve permission, when they review a draft, then they can approve or request changes; approval status and approver userId/timestamp are recorded in the audit log. - Given concurrent edits to the same draft, when a stale client submits an update without the current ETag/If-Match, then the system returns 409 Conflict and preserves the latest server version. - Given any edit action, when it is saved, then the audit log stores before/after values (diff), userId, reason, version ID, and timestamp (UTC).
Publish and Rollback Control
- Given a user with Publish Decay Settings permission, when they publish an approved draft, then the draft becomes Active with a new version ID and all downstream calculations use the new half-life immediately after confirmation. - Given a user without Publish Decay Settings permission, when they attempt to publish or rollback, then the system returns 403 and logs the denied attempt. - Given an active version history, when a rollback is initiated by a user with Publish permission, then the system restores the selected prior version exactly and creates a new version entry referencing the source version. - Given any publish or rollback, when it completes, then an audit entry is recorded with action (publish/rollback), fromVersion, toVersion, userId, change reason (if provided), and timestamp (UTC).
SSO Group-to-Role Mapping and Sync
- Given workspace role-to-IdP group mappings are configured, when a user’s IdP group membership changes, then the user’s EchoLens role updates to match within 5 minutes of the change. - Given a user is deprovisioned in the IdP, when they attempt to access EchoLens or use an API token, then access is denied within 15 minutes and any active session is invalidated at next request. - Given an unmapped user logs in via SSO, when role resolution occurs, then the user is assigned the least-privilege role with no Decay Tuner access by default. - Given role changes via SSO, when the user next loads Decay Tuner or calls related APIs, then permissions (view/edit/approve/publish) reflect the updated role without requiring manual intervention and the role-change is logged.
Audit Log Completeness and Integrity for Decay Tuner
- Given a user performs a preview, edit, approve, publish, or rollback in Decay Tuner, when the action occurs, then an audit record is created capturing action type, userId, user email, object (decay-setting scope), before/after values (or preview parameters), version ID, requestId, IP, and UTC timestamp in ISO 8601. - Given audit records exist, when they are queried, then results are immutable and append-only; there is no UI or API to alter or delete existing audit entries. - Given any attempt to call a non-existent modify/delete endpoint for audit entries, when the request is made, then the system returns 405 and logs the attempt. - Given filters for user, action type, date range, and object ID, when applied to the audit log view/API, then results are accurately filtered and paginated with stable, deterministic ordering by timestamp then requestId.
Audit Log Export for Compliance Reviews
- Given a user with Export Audit Logs permission, when they request an export with a date range and filters, then the system generates a downloadable CSV and JSON containing all matching entries with defined fields and UTC timestamps. - Given the export completes, when the file is downloaded, then it includes a header row/keys, uses UTF-8 encoding, and matches the on-screen filtered results count. - Given large exports exceeding the streaming threshold, when requested, then the system processes the export asynchronously and emails/notifies the requester with a time-limited signed download link. - Given any export is created, when it completes, then an audit entry records exporter userId, filter parameters, format, record count, and timestamp (UTC).
Uniform Permission Enforcement Across UI and API
- Given permissions are evaluated server-side, when a user lacks a specific permission (view/edit/approve/publish), then corresponding UI controls are hidden/disabled and API endpoints return 403 with a stable error code (e.g., DECAY_PERM_DENIED). - Given a user attempts to bypass the UI using direct API calls, when they invoke restricted endpoints, then no side effects are applied and the denial is logged with userId and route. - Given read paths include related entities (e.g., theme type, segment, channel), when a user has partial access, then responses are filtered to only include authorized scopes, with counts and aggregates reflecting only authorized data. - Given webhooks or outbound integrations are configured, when the subscriber lacks authorization to decay settings, then payloads exclude decay configuration details and an omission reason is recorded in the audit log.
API and Webhook Propagation
"As a platform integrator, I want APIs and webhooks for decay settings and outcomes so that internal tools stay synchronized with EchoLens automatically."
Description

REST/GraphQL endpoints to read, create, update, and version decay settings plus endpoints to retrieve preview results. Webhooks notify subscribers of publishes, rollbacks, and recompute completion. Ensures Jira/Linear sync respects current decay rules and pushes updated priorities when rankings change. Includes auth, rate limiting, and backward-compatible schema evolution.

Acceptance Criteria
Create and Version Decay Settings via REST and GraphQL
- Given a valid access token or API key and a well-formed payload with themeScope (themeType|segment|channel), halfLifeHours (1–720), and effectiveAt (ISO-8601), When the client calls POST /v1/decay-settings or GraphQL mutation createDecaySetting, Then a new setting is created with version incremented (starting at 1), 201 (REST) or success (GraphQL), and response includes id, version, etag, createdAt, effectiveAt, and actorId. - Given an invalid payload (missing required field, halfLifeHours out of range, unknown scope), When the request is submitted, Then the API returns 400 with machine-readable errors containing code, field, and message for each violation. - Given an existing setting, When the client updates via PUT /v1/decay-settings/{id} or mutation updateDecaySetting and supplies If-Match with a stale etag, Then the API returns 412 Precondition Failed and does not create a new version. - Given publish=true on update, When the request succeeds, Then the version status is "published" and the previously published version is marked "superseded" and remains readable via GET/GraphQL query.
Preview Decay Impact and Heatbar
- Given a draft or proposed decay setting, When GET /v1/decay-settings/{id}/preview?from=...&to=... or GraphQL query previewDecayImpact is called, Then the response includes heatbar[], rankedItems[], confidenceDelta, and computedAt, and REST/GraphQL outputs are consistent for the same inputs. - Given the same inputs, When the preview is called twice within 5 minutes with no new data, Then results are deterministic (content hash identical) and include X-Cache: HIT on repeat. - Given a large dataset (>=100k signals in window), When preview is requested, Then p95 latency <= 1500 ms and rankedItems are paginated via opaque cursor (pageSize configurable up to 200).
Webhook Notifications for Publish, Rollback, and Recompute Completion
- Given a decay setting version is published or rolled back, When the event occurs, Then a webhook with type "decay.published" or "decay.rolled_back" is delivered within 10s to active subscriptions and includes eventId, type, occurredAt, settingId, version, and actorId. - Given a recompute finishes due to publish/rollback, When processing completes, Then a "decay.recompute_completed" webhook is sent with affectedRankingsCount and window parameters. - Given webhooks are delivered, When the subscriber validates X-EchoLens-Signature (HMAC-SHA256) using the shared secret and checks X-EchoLens-Timestamp skew <= 300s, Then validation passes and event is accepted once per X-Idempotency-Key. - Given a subscriber endpoint returns 5xx or 429, When delivering a webhook, Then retries use exponential backoff up to 9 attempts; 2xx is success; 4xx (except 429) is not retried; undeliverable events are dead-lettered and visible via GET /v1/webhooks/dlq.
Jira/Linear Priority Sync on Ranking Changes
- Given rankings change due to updated decay rules, When recompute completes, Then only items with priority deltas are updated in Jira/Linear within 60s and include a comment referencing settingId and version. - Given vendor rate limits apply, When pushing updates, Then calls respect vendor quotas and retries without duplicating changes using an idempotency key per (externalIssueId, version). - Given a sync failure occurs after max retries, When it happens, Then the item is marked sync_failed with error details in the audit log and a webhook "sync.item_failed" is emitted.
Authentication and Rate Limiting
- Given a request includes a valid OAuth2 client-credentials token or API key with scopes (decay:read, decay:write as applicable), When calling any decay settings or preview endpoint, Then authorized requests succeed; otherwise, 401 (invalid/missing) or 403 (insufficient_scope) is returned with WWW-Authenticate details. - Given repeated requests from a client, When responses are returned, Then X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers are present; upon exceeding limits, 429 is returned with Retry-After (seconds). - Given a token is expired or revoked, When used, Then the API returns 401 with error=invalid_token and no state changes occur.
Backward-Compatible Schema Evolution
- Given a new optional field is added to decay settings or preview responses, When older clients call existing endpoints, Then responses remain backward compatible and new fields are optional and documented in schema metadata. - Given a breaking change is needed, When introducing it, Then a new versioned media type (Accept: application/vnd.echolens.v2+json) or versioned GraphQL field is provided; v1 continues to function for >=90 days with Sunset and Deprecation headers. - Given a client requests a specific version via Accept negotiation, When the server responds, Then Content-Type reflects the negotiated version and the payload matches that version's schema with a valid $schema or __typename indicator.

Signal Split

See exactly how severity, velocity, and customer value contribute to a theme’s health—complete with adjustable weights and inline examples. Builds stakeholder trust and speeds decisions by exposing the ‘why’ behind the score so PMs and CX can target the right lever to move the needle.

Requirements

Weighted Scoring Engine
"As a product manager, I want a transparent weighted score that shows how severity, velocity, and customer value contribute so that I can understand and justify prioritization decisions."
Description

Implement a backend scoring service that calculates a theme’s health as a weighted composite of severity, velocity, and customer value. Normalize inputs across sources (reviews, emails, tickets, chats), handle missing or delayed signals gracefully, and output both the final score and per-signal contribution percentages. Provide real-time recomputation on data ingestion and on weight changes with sub-second latency for top-N theme lists, and eventual consistency for the full index. Expose an explain API that returns inputs, normalization steps, applied weights, and intermediate values for transparency. Include safeguards against extreme outliers, configurable caps, and versioned formulas to support future signal additions. Ensure robust test coverage, observability (metrics/traces/logs), and horizontal scalability to weekly shipping teams’ data volumes.

Acceptance Criteria
Weighted Composite & Contributions Correctness
Given normalized signals S=0.80, V=0.40, C=0.60 and weights ws=0.50, wv=0.30, wc=0.20 for theme T When the engine computes health Then final_score equals 0.62 ±0.001 and contribution_percentages equal [64.516%, 19.355%, 16.129%] ±0.1% and sum to 100% ±0.1% Given non-negative weights that do not sum to 1 (e.g., 5,3,2) When the engine computes health Then weights are normalized to sum to 1.0 ±0.001 and computation uses normalized weights Given any zero final_score When contributions are computed Then all contribution_percentages are 0% and no division-by-zero occurs
Source Normalization Across Channels
Given the published test corpus v1 with raw inputs across reviews, emails, tickets, and chats When normalization runs Then all normalized signals are in [0,1] and match the golden outputs within ±0.01 Given the cross-channel equivalency set v1 When normalization runs Then per-signal normalized values for equivalent items differ by ≤ 0.02 across channels Given the same input processed twice When normalization runs Then outputs are deterministic and byte-identical across runs
Graceful Handling of Missing/Delayed Signals
Given severity=0.7 and customer_value=0.5 are available, velocity is missing, and staleness_window=2h with weights ws=0.4, wv=0.4, wc=0.2 When the engine computes health Then effective weights are renormalized to ws'=0.667, wc'=0.333, wv'=0.0 and final_score equals 0.667*0.7 + 0.333*0.5 = 0.633 ±0.001 Given any missing signal When the explain API is called Then the response marks the signal as missing with reason and timestamp and sets its contribution_percentage to 0% Given a delayed signal arrives after being missing When ingested Then the theme’s score is recomputed and the top-N list reflects the change within 1s P95
Real-Time Recompute & Eventual Consistency
Given 100k themes indexed and a steady ingest of 100 events/sec When a top-N list (N=20) is requested and a global weight change is applied Then the top-N response latency is ≤ 500ms P95 and ≤ 900ms P99 and results reflect the new weights Given the same conditions When a batch of 1k new events is ingested Then the top-N response latency remains within the above SLOs and reflects new data Given a global weight change or ingestion burst When observing the full index Then all theme scores converge within 60s (eventual consistency) and no theme remains stale beyond 90s
Explain API Transparency
Given a theme_id and current weights When GET /v1/score/explain is called Then the response includes: inputs.raw, inputs.normalized, normalization_steps, weights.applied, formula_version, weighted_values, caps_applied, final_score, contribution_percentages and the JSON validates against the OpenAPI schema Given the explain payload When recomputing final_score from the payload client-side Then the reconstructed score matches the API final_score within ±0.001 Given a request budget of 500ms P95 When calling the explain API under 10 RPS Then latency is ≤ 700ms P95 and ≥ 99.9% of responses are HTTP 200
Outlier Safeguards & Configurable Caps
Given inputs containing extreme outliers (≥ P99.9) When normalization runs Then values are clamped to the configured caps and the explain payload indicates cap_applied=true with cap_value Given clamped values When scoring runs Then final_score remains within [0,1], no NaN/Inf occurs, and contribution_percentages sum to 100% ±0.1% Given cap settings are changed at runtime When applied Then subsequent computations use the new caps and the cap configuration version is included in explain
Observability & Test Coverage
Given the scoring service is running When scraping /metrics Then it exposes counters and histograms for scoring_requests_total, scoring_latency_ms, explain_requests_total, normalization_errors_total, outlier_caps_applied_total, recompute_queue_depth Given a trace is captured for a scoring request When inspecting spans Then a span named scoring.compute exists with attributes theme_id, formula_version, weights_hash, normalization_version and it propagates W3C trace context across services Given CI executes the test suite When measuring coverage for the scoring module Then line coverage ≥ 85% and branch coverage ≥ 80% with property-based tests for weight normalization and golden-file tests for normalization
Weight Controls & What‑If UI
"As a CX lead, I want to adjust weights and preview the impact before publishing so that I can tune prioritization without disrupting the team’s live queues."
Description

Provide an accessible UI with sliders/inputs to adjust weights for severity, velocity, and customer value, enforcing validation (e.g., sums to 100%) and sensible bounds. Support draft vs. published modes, instant what‑if previews with live delta indicators on ranks, and a reset-to-default option. Allow scoping of weight sets at workspace, product area, and saved-filter levels, with clear inheritance and override indicators. Include undo/redo, keyboard navigation, and autosave drafts. Optimize for snappy feedback (<300 ms) using local caching and batched recompute calls.

Acceptance Criteria
Validate weight inputs and sum to 100
- Given weight sliders and numeric inputs for Severity, Velocity, and Customer Value, when a user enters any value outside 0–100, then the input is prevented or clamped and an inline error is shown until corrected. - Given weight inputs accept decimals to one decimal place, when the user types values, then the UI rounds display to 0.1 and uses rounded values for validation. - Given three weight values, when their rounded sum is not exactly 100.0, then the Publish action is disabled and an inline message "Weights must sum to 100%" is displayed; when the sum equals 100.0, the message clears and Publish is enabled. - Given validation errors are present, when the user fixes inputs so the sum is 100.0 and all values are within bounds, then the error state clears without page reload.
Draft vs published modes with autosave
- Given a valid weights change, when the user adjusts any weight, then a Draft state indicator appears and the draft auto-saves within 1 second of the last change. - Given an auto-saved draft, when the user reloads or navigates back to the page, then the last draft values are restored for that scope. - Given a valid draft, when the user clicks Publish, then the draft becomes the Published set, a timestamp and actor are recorded, and the Draft indicator clears. - Given a new publication, when another user at the same scope opens the UI, then they see the newly published weights within their next refresh session (no older than 60 seconds if a long-lived session).
Instant what-if preview with rank deltas
- Given a valid dataset is loaded, when a user changes any weight in Draft, then the theme ranking list and theme scores update with deltas within 300 ms for p95 of interactions. - Given deltas are shown, when ranks change, then each affected theme displays an inline delta indicator with direction (up/down), absolute rank change, and score delta rounded to 0.1. - Given multiple rapid changes (<200 ms apart), when the user adjusts weights repeatedly, then at most one batched recompute call is sent per 200 ms window. - Given a previously computed weight combination, when the user returns to those exact values, then the preview uses cached results (no network call) and renders within 100 ms for p95.
Reset to default weights
- Given a draft differs from the default for the current scope, when the user clicks Reset to Default, then the weights revert to the scope’s default values and validation passes. - Given Reset is invoked, when the action would discard non-published changes, then the user is prompted to confirm; on confirm the Draft reflects defaults and autosaves; on cancel no change occurs. - Given defaults are restored, when the preview recomputes, then ranks/deltas update within 300 ms for p95 and the delta indicators reflect changes versus the pre-reset draft.
Scoped weights with inheritance and overrides
- Given scopes Workspace, Product Area, and Saved Filter, when viewing a scope, then the UI displays the effective weights and a badge indicating Inherited or Overridden, including the source scope name. - Given a child scope with inherited weights, when the user edits any weight, then the scope switches to Override mode with a clear indicator and a Revert to Inherit control becomes available. - Given an overridden child scope, when the user clicks Revert to Inherit, then the effective weights match the parent scope and the Override indicator clears. - Given the hierarchy Workspace < Product Area < Saved Filter, when resolving effective weights, then the nearest override wins and is consistently applied across UI and recompute API. - Given drafts are scope-specific, when switching scopes, then each scope’s draft is preserved independently and restored upon return.
Undo/redo for weight adjustments
- Given a series of weight changes in a draft, when the user presses Undo (Ctrl/Cmd+Z), then the previous weight state is restored and the preview updates within 300 ms. - Given available undo history, when the user presses Redo (Ctrl/Cmd+Shift+Z), then the next weight state is restored and the preview updates within 300 ms. - Given no further history in a direction, when the user attempts Undo/Redo, then the corresponding control is disabled and no change occurs. - Given autosave is enabled, when undo/redo is used, then history is preserved across autosave events and maintains at least the last 50 steps.
Keyboard navigation and accessibility
- Given only a keyboard, when navigating the control set, then focus order follows labels -> sliders/inputs -> helper actions (Reset, Publish) and all elements are reachable. - Given a slider is focused, when the user presses Left/Right, then the value changes by 1; when pressing Shift+Left/Right, the value changes by 10; values remain within 0–100 and validation updates live. - Given numeric inputs are focused, when typing valid numbers and pressing Enter, then the value applies and validation/preview update accordingly. - Given assistive technologies, when reading controls, then each slider/input exposes an accessible name (e.g., "Severity weight"), role=slider or spinbutton, and accurate aria-valuemin/aria-valuemax/aria-valuenow. - Given visual contrast requirements, when rendered in default theme, then text and interactive components meet WCAG 2.1 AA contrast (text >= 4.5:1; UI components >= 3:1).
Contribution Breakdown Visualization
"As a stakeholder, I want a clear visual breakdown of what drives a theme’s score so that I can trust the ranking and discuss trade‑offs confidently."
Description

Render an inline breakdown for each theme showing how each signal contributes to the health score via percentages, absolute values, and trend indicators (e.g., velocity sparkline). Use consistent color semantics, tooltips with formula snippets, and expandable details to reveal raw signal values and normalization ranges. Embed the breakdown in theme list rows, theme detail views, and the live theme map. Provide export (PNG/CSV) and copy-to-clipboard for stakeholder sharing, with WCAG-compliant contrast and screen-reader labels.

Acceptance Criteria
Inline Breakdown in Theme List Rows
Given the Themes list page is loaded and contains at least one theme, When a theme row is rendered, Then the row displays a contribution breakdown component with three labeled signals: "Severity", "Velocity", and "Customer Value". And each signal displays a percentage value with one decimal precision (e.g., 42.3%). And the sum of the three displayed percentages equals 100% with at most ±1% variance due to rounding. And each signal displays its absolute value used in the health score computation adjacent to its percentage. And a velocity sparkline is rendered within the component and is non-empty when velocity data is present. And the component’s layout and data match the theme’s detail view for the same theme.
Breakdown in Theme Detail View with Expandable Raw Values
Given a user opens a Theme Detail view, When the contribution breakdown is visible, Then an affordance to expand "Details" is present and keyboard focusable. And when the user activates the expander, Then a panel reveals for each signal: raw current value, normalization min, normalization max, and the normalized value used to compute the percentage. And the revealed values match those used in the latest computed health score for that theme. And the panel shows the timestamp of the data used for normalization and indicates the source stream count for raw values where applicable. And collapsing the panel hides the details without changing the computed percentages.
Breakdown on Live Theme Map
Given the Live Theme Map is displayed, When a theme node is selected via click or keyboard, Then the same contribution breakdown component is shown inline for the selected theme. And the numbers, labels, colors, and sparkline match those shown for the same theme in the list and detail views. And when new data arrives and the map updates, Then the breakdown values update on the next refresh cycle without page reload.
Tooltip Formula Snippets
Given any contribution breakdown component is visible, When the user hovers or focuses the overall health score or any signal entry, Then a tooltip appears within 300 ms containing a formula snippet used to compute the health score contribution for that element. And the tooltip includes variable names, normalization references, and weights if applicable, in the form Score = f(Severity, Velocity, Customer Value, …). And the tooltip content is identical across Theme List, Theme Detail, and Live Theme Map for the same theme and signal. And the tooltip is dismissible via Escape and is reachable via keyboard focus.
Color Semantics and Accessibility Compliance
Given any contribution breakdown is rendered, Then color semantics are consistent: the same signal uses the same base color token across all contexts, and intensity encodes relative contribution consistently. And all text and iconography in the component meet WCAG 2.1 AA contrast (>= 4.5:1 for normal text; >= 3:1 for large text and graphical objects). And each interactive element and data point has an accessible name and description (aria-label/aria-describedby) that includes the signal name, percentage, absolute value, and trend direction if present. And all interactions (hover tooltips, expand/collapse, selection) are operable via keyboard only and reachable via a logical tab order. And the sparkline includes a screen-reader-only summary of trend direction (up/down/flat) and period.
Export and Copy-to-Clipboard
Given a user opens the export menu on a contribution breakdown, When the user selects "Export PNG", Then a PNG file downloads with a filename in the format {theme_slug}_breakdown_{YYYY-MM-DDThhmmss}.png and visually matches the on-screen component (labels, percentages, absolute values, sparkline, colors). And when the user selects "Export CSV", Then a CSV downloads containing a header row and one row per signal with columns: signal_name, percent, absolute_value, normalization_min, normalization_max, normalized_value, timestamp. And when the user selects "Copy to clipboard", Then the clipboard contains a plain-text tabular representation with the same columns and values as the CSV. And exports and clipboard reflect the latest visible data for the selected theme.
Inline Examples & Evidence Surfacing
"As a product manager, I want concrete examples inline with the score so that I can quickly validate the drivers without switching tools."
Description

Surface representative, de-duplicated examples (tickets, reviews, chats) that illustrate severity, velocity, and customer value directly within the breakdown. Implement sampling that balances recency, diversity of sources, and impact, with badges indicating why each example was selected. Provide links back to the original system, PII redaction, quick filters (source, segment, product area), and lazy-loading pagination. Cache results to minimize recompute while reflecting new data in near real time.

Acceptance Criteria
Per-Dimension Inline Example Display
Given a theme with >= 3 items mapped to Severity, Velocity, and Customer Value When a user opens the Signal Split breakdown for that theme Then each dimension panel renders a list of representative examples within 1500 ms at p75 And the initial load shows up to 10 examples per dimension (minimum 3 if available) And each example displays source, timestamp, and a 280-character snippet with the matched dimension highlighted And when the user scrolls to the end of a panel, the next page of 20 examples is lazily loaded within 800 ms at p75 without duplicating items already shown And if a dimension has no examples, the panel displays 'No examples yet' with a link to filtering guidance
Sampling Balance and Selection Badges
Given an eligible pool of examples for a theme When the system samples examples for display Then at least 30% of surfaced examples are from the last 7 days when >= 5 such items exist And examples span at least 3 distinct sources (e.g., tickets, reviews, chats, emails) when available; otherwise all distinct sources are represented And at least 40% of surfaced examples come from the top quartile of impact score for the theme (when >= 10 items exist) And each surfaced example shows 1–3 selection badges chosen from {Recent, High Impact, Diverse Sources, Priority Segment, High Severity, Fast-Rising} And hovering or focusing a badge reveals a tooltip that explains the selection rule in <= 2 short sentences And every surfaced example has at least 1 badge
Deduplication and Similarity Merging
Given the raw evidence set contains duplicates or near-duplicates When preparing the example list Then exact duplicate texts are removed And items with semantic similarity >= 0.90 are consolidated under a single displayed example with a 'x similar merged' indicator And clicking 'x similar merged' expands to list the merged item IDs and sources And no two displayed examples have identical text And a summary line shows 'Showing N of M items (K merged)'
PII Redaction of Snippets and Metadata
Given examples may include PII When rendering snippets or metadata in EchoLens Then email addresses, phone numbers, postal addresses, credit card numbers (last 4 only), and full personal names are masked according to the product redaction rules And redaction occurs before caching and display And copying or exporting the snippet preserves the redaction And no unredacted PII is visible in the UI, logs, telemetry, or network responses used by the UI
Deep Links to Original Systems
Given each example originates from an external system When the example is displayed Then a deep link labeled with the source system icon and name is shown And activating the link opens the original item in a new tab and returns HTTP 200/302 within 3 seconds in integration tests And if the link is unavailable or the user lacks permission, a disabled 'Link unavailable' state with a tooltip is shown instead And link URLs are templated using the configured connector without exposing credentials client-side
Quick Filters for Source, Segment, and Product Area
Given filter controls for Source, Segment, and Product Area are present above the example list When the user selects one or more filter values Then the example list updates to show only matching items within 400 ms at p75 And active filters are displayed as removable chips and encoded in the URL so the state can be shared or reloaded And combining filters uses AND logic across categories and OR logic within a category And the count of displayed examples updates accurately And clearing all filters returns to the default sample
Caching and Near Real-time Refresh
Given cached example lists exist for a theme and filter state When a user revisits the theme within 15 minutes Then the cached results render within 800 ms at p75 And the system performs a background refresh (stale-while-revalidate) and updates the list if new data is available And newly ingested evidence for the theme appears in the UI within 2 minutes of ingestion (p95), or immediately when the user presses 'Refresh' And a manual 'Refresh' action recomputes and re-renders within 3 seconds at p75 And cache keys include theme ID, dimension, and filter state to avoid cross-contamination
Weight Change Governance & Audit
"As an admin, I want audited and controllable weight changes so that our prioritization remains compliant and trustworthy."
Description

Introduce role-based permissions for editing weights, with optional two-step approval for workspace-level changes. Maintain a versioned audit log capturing editor, timestamp, diffs, rationale notes, and affected scopes. Support scheduling effective dates, rollback to prior versions, and notifications to watchers on publish. Expose a read API for current and historical weight sets to enable reporting and compliance reviews.

Acceptance Criteria
Enforce role-based permissions on weight edits
Given a user with role "Weight Editor" or "Admin" in workspace W, When they open Signal Split weights at workspace scope, Then the Edit action is enabled. Given a user without edit permission in workspace W, When they attempt to modify weights via UI or API, Then the request is rejected with 403 and no changes are persisted. Given an editor with project-only permissions, When they attempt to edit workspace-level weights, Then the request is rejected with 403. Given an eligible editor, When they save changes, Then the system saves a draft version tagged with their user ID and timestamp.
Two-step approval for workspace-level changes
Given two-step approval is enabled for workspace W, When a user submits a weight change for workspace scope, Then the system creates a Pending Change requiring approval from a different user with Approver or Admin role. Given the author attempts to approve their own pending change, Then the system blocks the action with error "Self-approval not permitted". Given an approver approves the pending change and no schedule is set, Then the change is published immediately and becomes the current version. Given an approver rejects the pending change, Then the draft returns to Draft state with a required rejection reason captured. Given any approval or rejection event, Then the audit log entry includes approver ID, timestamp, decision, and rationale.
Versioned audit log for weight changes
Given any create, update, approve, reject, publish, schedule, or rollback action on weight sets, Then an immutable audit entry is recorded with actor ID, timestamp (ISO 8601 UTC), scope, version IDs (from/to), change diff per weight dimension, and rationale notes if provided. Given the audit log is queried by time range and scope, Then entries are returned in chronological order and are filterable by action type. Given a diff is recorded, Then each dimension shows old_value, new_value, and delta with precision to 0.01. Given an audit entry is created, Then it cannot be edited or deleted by any role.
Schedule effective date/time for weight changes
Given an eligible editor sets a future effective date/time using the workspace default timezone, Then the system accepts only times greater than or equal to the current time and stores the schedule. Given a scheduled change exists, When the effective time is reached, Then the system publishes the version atomically and marks it Current within 60 seconds. Given a scheduled change exists, Then the current active weights remain unchanged until the effective time. Given a scheduled publish fails, Then the system retries up to 3 times and notifies watchers of the failure with error details.
Rollback to a prior weight version
Given an authorized user selects prior version Vn and initiates rollback, Then the system creates a new version Vn+1 identical to Vn and sets it as Pending (or Pending Approval if two-step is enabled). Given two-step approval is enabled, When rollback is initiated, Then an approver distinct from the initiator must approve before Vn+1 becomes Current. Given rollback completes, Then the audit log records action "rollback" with initiator ID, target_version, new_version, and rationale. Given rollback is completed, Then all clients reading current weights receive Vn+1 on next read within 10 seconds.
Notify watchers on publish and schedule events
Given a change is published (immediate or scheduled), Then all watchers of the weight set receive in-app notifications within 60 seconds and email notifications within 5 minutes including version ID, scope, effective time, author or approver, and top 3 diffs. Given a change is scheduled, Then watchers receive a scheduled notification at creation time including effective time and link to review. Given a scheduled publish fails or is canceled, Then watchers receive a failure or cancelation notification with reason within 60 seconds. Given a watcher unsubscribes from weight-set notifications, Then no further notifications are sent to that user while audit and delivery logs reflect the opt-out.
Read API for current and historical weight sets
Given a client calls GET /weights/current?scope={scope_id}, Then the API returns 200 with the current version payload: version_id, scope, effective_from, weights, approval_status, last_modified, and ETag. Given a client calls GET /weights/history?scope={scope_id}&from={ts}&to={ts}, Then the API returns 200 with a paginated list of versions with metadata and diffs ordered by effective_from descending. Given a client calls GET /weights/at?scope={scope_id}&ts={timestamp}, Then the API returns the version effective at that timestamp or 404 if none exists for the scope. Given the caller lacks read permission for the scope, Then the API returns 403 and no data. Given If-None-Match with a matching ETag on /weights/current, Then the API returns 304 Not Modified.
Segment-Specific Weight Profiles
"As a product manager, I want different weight profiles for enterprise vs. SMB segments so that prioritization reflects business value across customer groups."
Description

Enable creation of named weight profiles targeted to segments such as customer tier, ARR band, region, or product area. Provide rule-based assignment with priority order and conflict resolution, plus a clear preview of which profile applies to a given theme. Support inheritance from global defaults, bulk apply to saved views, and analytics showing profile usage and outcome shifts over time.

Acceptance Criteria
Create and validate a named segment-specific weight profile
Given I am in Signal Split > Weight Profiles settings When I create a profile named "Enterprise_NA" with severity=50, velocity=30, customer_value=20 (total=100), and rules: tier=Enterprise AND region=NA AND product_area IN [Billing, Payments] Then Save is enabled, the profile is persisted, appears in the list with Active status, and is available in rule assignment Given a duplicate profile name (case-insensitive) Then Save is blocked with inline error "Name must be unique" Given weights do not sum to 100 or a weight is outside 0–100 Then Save is disabled and an inline validator indicates the issue Given no rules are defined Then the profile can be saved but is labeled "Unassigned" and never auto-applied
Rule-based assignment with priority order and deterministic conflict resolution
Given multiple profiles match a theme When evaluating assignment Then the profile with the lowest priority rank (1 is highest) is applied And if priorities tie, the profile with the greater number of matching predicates is applied And if still tied, the most recently updated profile is applied Given an admin reorders priorities and saves Then all affected themes are re-evaluated and reflect the new applied profile within 60 seconds And per-theme evaluation latency is <=150 ms at P95
Applied profile preview on a theme
Given a theme detail pane When I open the "Applied Profile" preview Then I see the profile name, priority, matched predicates, unmatched predicates, and the effective weights (severity/velocity/customer_value) And I see a concise explanation "Applied because: tier=Enterprise, region=NA matched; higher priority than SMB_Global" Given no profile rules match Then the preview clearly states "Global Defaults applied" and shows the default weights And the preview loads in <=300 ms at P95
Inheritance from global defaults with override behavior
Given global default weights are severity=40, velocity=40, customer_value=20 And a child profile overrides severity to 55 only When viewing effective weights Then severity=55, velocity=40, customer_value=20 When the global default velocity changes to 35 Then all child profiles that do not override velocity reflect velocity=35 within 60 seconds; overridden fields remain unchanged And a "Reset to defaults" action on a profile restores all fields to inherit and records an audit entry
Bulk apply a weight profile to a saved view
Given a saved view V with filters and 10,000 themes When I set weight profile P as the scoring profile for V and confirm Then V’s rankings recompute using P without modifying theme-level assignment rules And progress feedback is shown, completion occurs within 2 minutes for up to 10,000 themes, and an Undo option is available for 15 minutes And the chosen profile persists with the view and is respected in share links
Analytics: profile usage and outcome shift over time
Given a date range is selected When viewing Profile Analytics Then I see a time series of themes scored by each profile, the percentage share, and a table of top-20 theme rank changes attributable to profile switches And I can compare any two periods (e.g., last 7 days vs prior 7 days) to see outcome shifts with deltas And I can export CSV of usage and shifts And metrics update at least daily and within 24 hours of changes
Score Component Sync to Jira/Linear
"As a product manager, I want the score and its drivers synced to Jira/Linear so that engineering can see context without leaving their delivery tools."
Description

Extend existing sync to push the overall score, individual signal contributions, applied weight profile name, and last-updated timestamps into mapped custom fields in Jira/Linear. Update on publish and on data-driven recomputes with idempotent operations, backoff/retry, and conflict handling. Provide a mapping UI for field selection/creation, permission checks, and error monitoring with alerts.

Acceptance Criteria
Mapping UI: Field Selection, Creation, and Permission Validation
Given a connected Jira or Linear integration and a selected project When the user opens the Score Component Sync mapping UI Then they can map Overall Score, Severity Contribution, Velocity Contribution, Customer Value Contribution, Weight Profile Name, and Last Updated Timestamp to existing custom fields. Given a required field does not exist When the user selects Create Field Then the platform creates the field with the correct data type (Number for scores/contributions, Text for weight profile name, DateTime for last updated) and confirms success within 10 seconds. Given the user lacks permission to create or write to a field When attempting to save the mapping Then the save is blocked with a descriptive error, a non-200 integration event is logged, and an inline Request Admin Access action is shown. Given mappings are saved When the user clicks Test Write Then the system validates write access for each mapped field and returns pass/fail per field. Given both Jira and Linear are connected When switching target system in the UI Then mappings are preserved per system and do not overwrite each other.
On Publish Sync: Push Scores and Components
Given a theme has a computed score and valid mappings When a user clicks Publish Then the system pushes the Overall Score, Severity, Velocity, and Customer Value contributions, Weight Profile Name, and Last Updated Timestamp to the mapped fields on the linked Jira/Linear issue within 5 seconds. Given a mapped field is missing remotely or is of incompatible type When syncing on publish Then the system skips only the invalid field, surfaces a field-specific error in the sync panel, and still updates all valid fields. Given the remote values already match the computed values When syncing on publish Then the operation is a no-op with zero additional remote writes and no duplicate comments or history entries. Given API rate limits are encountered When syncing on publish Then the system retries with exponential backoff (initial 1s, max 32s) up to 5 attempts before marking the sync as failed.
Data-Driven Recomputation Sync
Given new feedback changes a theme’s computed score When the recompute completes Then the system enqueues an update and updates mapped fields to the latest values within 2 minutes. Given multiple recomputes occur within a 60-second window When processing updates Then the system coalesces them and sends only the final state. Given the recomputed values are unchanged within a tolerance of 0.01 When evaluating Then no remote update is sent. Given a retryable error occurs When processing the job Then retries follow exponential backoff up to 5 attempts and then an alert is emitted.
Idempotency and De-duplication
Given a sync job is retried or received more than once When issuing remote writes Then an idempotency key derived from issueId and payload hash prevents duplicate updates. Given the same publish action is triggered twice within 60 seconds When processing Then only one remote update occurs. Given the queue contains duplicate jobs for the same issue and payload When dequeuing Then duplicates are deduplicated prior to execution.
Conflict Detection and Safe Resolution
Given the remote issue version/etag has changed since last read When updating mapped fields Then the system detects the conflict, fetches the latest version, reapplies intended changes, and retries up to 3 times. Given conflicts persist after retries When handling the job Then the system marks the sync as failed, surfaces a descriptive message with correlation ID in the sync panel, and triggers an alert. Given a conflict is resolved by retry When the update succeeds Then the activity log records both the conflict and the successful resolution.
Data Integrity and Formatting
Given values are sent to remote systems When writing fields Then numeric values are rounded to 2 decimal places and the sum of contributions equals the overall score within a tolerance of 0.01. Given timestamps are written When writing Last Updated Timestamp Then the value is in ISO 8601 UTC format (e.g., 2025-08-17T12:34:56Z). Given Jira and Linear have differing field type requirements When creating or mapping fields Then the system validates and maps to the correct remote field types and rejects incompatible types with a clear error. Given a value becomes null When syncing Then the remote field is cleared and the change is recorded in the activity log.
Observability, Errors, and Alerts
Given any sync attempt occurs When it completes Then the system records audit metadata (themeId, issueId, target system, payload hash, status, attempt count, latency, response code, timestamp) and exposes it in an activity log. Given failures exceed 5% of attempts over a rolling 15-minute window or an individual issue fails 3 consecutive times When monitoring Then an alert is generated to the owning workspace via email and in-app notification. Given a single-field failure in a multi-field sync When reporting Then the UI shows field-level pass/fail with actionable messages and a Retry button.

Confidence Bands

Wrap every health score with a sample‑aware confidence band and data‑quality badge. Prevents overreaction to thin data and highlights when to gather more evidence, helping Data‑Driven Prioritizers justify when to act now versus wait for signal clarity.

Requirements

Confidence Band Calculation Engine
"As a product manager, I want each health score to include a statistically sound confidence band so that I can judge how reliable the signal is before prioritizing work."
Description

Implement a statistically sound engine that computes sample-aware confidence bands around each health score across themes, segments, and time slices. The engine should support streaming and batch inputs from reviews, emails, tickets, and chats; handle sparsity, outliers, and deduplication; and apply variance-aware techniques (e.g., standard error, bootstrap, or Bayesian credible intervals) with recency weighting and source reliability weighting. Outputs must include point estimate, lower/upper bounds, confidence level, raw N and effective sample size, method used, and data freshness. Provide graceful fallbacks when data are thin and standardize interfaces for downstream UI, alerts, and sync integrations.

Acceptance Criteria
Streaming and Batch Ingestion Parity
Given a streaming event for a specific theme/segment/time slice arrives When the engine processes the event Then an updated confidence band is emitted via the standard output interface within 5s at p95 and 15s at p99 Given a batch containing the same set of events previously streamed When the engine processes the batch Then the resulting point_estimate and bounds match the streaming result within an absolute tolerance of 0.5% and the method field matches exactly Given duplicate events with identical composite key (source_id, message_id) within a 30-day window When ingested via streaming or batch Then raw_n counts the event once, effective_n reflects deduplication, and audit.duplicates_count increases accordingly Given the same batch file is reprocessed When the engine runs Then outputs are idempotent (no double counting) and the result version identifier does not change
Confidence Band Method Selection and Correctness
Given effective_n >= 50 (default config) When computing intervals Then method = "SE_t" (standard error with t-distribution) and confidence_level defaults to 0.90 unless overridden Given 10 <= effective_n < 50 When computing intervals Then method = "Bootstrap" with >= 1000 resamples and a fixed seed produces reproducible bounds across runs (differences <= 0.1%) Given effective_n < 10 When computing intervals Then method = "Bayesian" and credible intervals widen relative to larger-N cases; method_params include prior summary Given any computation When results are produced Then lower_bound <= point_estimate <= upper_bound, bounds are finite, confidence_level is echoed, and if score bounds [min,max] are configured then bounds are clipped to [min,max] Given two datasets where the second adds 20% more uniformly sampled observations to the first When intervals are recomputed Then the median band width decreases by at least 10% (or stays equal) holding configuration constant
Thin Data Fallbacks and Data-Quality Badging
Given effective_n = 0 for a theme/segment/time slice When computing outputs Then point_estimate = null, lower_bound = null, upper_bound = null, method = "NoData", effective_n = 0, and badges includes {code:"NO_DATA"} Given 0 < effective_n < min_n_threshold (default 30) When computing outputs Then badges includes {code:"LOW_SAMPLE"} and confidence_level escalates to thin_data_confidence_level (default 0.95) Given high dispersion is detected (coefficient of variation > cv_threshold) or bootstrap diagnostics fail When computing outputs Then badges includes {code:"HIGH_VARIANCE"} and method_params include diagnostic flags Given data_freshness exceeds staleness_threshold (default 7 days) When computing outputs Then badges includes {code:"STALE_DATA"} and data_freshness_seconds reflects the elapsed time Given any badge is present When outputs are emitted Then outputs remain machine-parseable and consumable by downstream systems with badges[] populated and no missing required fields
Outlier Handling and Deduplication Policy
Given a dataset containing values exceeding 3*MAD from the median When outlier handling is enabled Then outlier weights are capped at outlier_weight_cap (default 0.1), audit.outliers_count reflects the number capped, and the point_estimate shift versus an outlier-removed baseline is <= 5% Given repeated events with identical (source_id, message_id) or identical normalized_text_hash within a 24h window When ingested Then only one is counted toward raw_n and effective_n, audit.duplicates_count increases appropriately, and a dedup_reason is recorded Given outlier handling is toggled off in configuration When recomputing on the same dataset Then audit.outliers_count = 0 and the method and bounds change only as expected from the removal of outlier caps (no other fields regress)
Recency and Source Reliability Weighting
Given half_life_days = 14 is configured for exponential decay When weights are computed Then an event 14 days old has weight 0.5 ± 0.01 of a new event and an event 28 days old has weight 0.25 ± 0.01 Given per-source weights are configured (e.g., tickets:1.2, reviews:1.0, chats:0.8, emails:1.0) When events from multiple sources are combined Then effective_n equals the sum of (decay_weight * source_weight) across events, and method selection uses effective_n Given recency and source weighting are disabled (half_life = infinite, all source weights = 1) When recomputing on the same inputs Then effective_n = raw_n and the resulting point_estimate and bounds match the unweighted computation within numerical tolerance (<= 0.1%)
Output Schema and Metadata Completeness
Given any computed result When serialized to the standard output schema Then the payload includes fields with types: id:string, point_estimate:number, lower_bound:number|null, upper_bound:number|null, confidence_level:number, raw_n:integer, effective_n:number, method:string, method_params:object, data_freshness_seconds:integer, updated_at:ISO-8601 timestamp, window_start:ISO-8601 timestamp, window_end:ISO-8601 timestamp, badges:array, audit:object{duplicates_count:int,outliers_count:int,source_breakdown:object} Given a request specifying confidence_level = 0.95 When results are computed Then confidence_level = 0.95 in the output and bounds reflect the requested level Given inputs contain invalid numeric values (NaN/Inf) after parsing When computing Then such records are excluded from calculations, audit.invalid_count increases, and raw_n reflects only valid records
Standardized Interface for UI, Alerts, and Sync Integrations
Given the published JSON Schema version = "confidence_band.v1" When validators run on emitted payloads Then 100% of payloads validate successfully and include schema_version = "confidence_band.v1" Given a backwards-compatible addition (minor version bump) When v1 consumers parse the payload Then required fields remain unchanged and consumers operate without errors Given an invalid compute request (missing required parameters) When the API is called Then the service returns HTTP 400 with a structured error {code,message,field} Given the UI, alerts, and sync services request a paginated snapshot When the engine provides results Then results are returned in deterministic order (updated_at desc, id tie-break) with stable cursors and no duplicates across pages
Data Quality Scoring & Badging
"As a CX lead, I want a clear data-quality badge with specific reasons so that I can explain to stakeholders why we should act now or wait for better signal."
Description

Calculate a data quality score per theme and time window based on source mix diversity, recency, duplication rate, language completeness, model confidence, bot/noise detection, and anomaly screening. Map scores to clear badge levels (e.g., High, Medium, Low) with color and iconography, and expose drill-down reasons (e.g., low N, single-source dominance, stale data). Support organization-level thresholds, override controls with audit logs, and ensure badges are available to UI, exports, and downstream syncs.

Acceptance Criteria
Compute Data Quality Score per Theme and Window
Given a theme X within reporting window W and inputs present (source mix diversity, recency distribution, duplication rate, language completeness %, model confidence mean, bot/noise rate, anomaly flags) When the scoring job executes for (X, W) Then a deterministic quality_score in the range 0–100 is produced and stored with (theme_id, window_id, computed_at) And the score incorporates all listed factors with documented weights and unit tests covering each factor’s contribution And rerunning the job with identical inputs yields the same score And increasing duplication rate or bot/noise rate reduces the score; increasing source diversity, recency, or model confidence increases the score And the effective sample size (N_eff) is calculated and persisted for use in badges and reasons
Badge Level Mapping and UI Iconography
Given organization badge cutoffs High ≥ 80, Medium 50–79, Low < 50 When a theme has quality_score = 83 Then badge_level is High and UI renders a green badge with the High icon and label When quality_score = 65 Then badge_level is Medium and UI renders an amber badge with the Medium icon and label When quality_score = 42 Then badge_level is Low and UI renders a red badge with the Low icon and label And the mapping updates within 60 seconds after cutoff changes
Drill-Down Reasons Panel
Given a theme with badge_level = Low due to low N, single-source dominance, and stale data When the user opens the badge drill-down from Theme Detail Then the panel lists at least the top 3 contributors with metric and threshold, e.g., "Low sample size: N=12 (< min_N=30)", "Single-source dominance: Reviews=85% (> 70%)", "Stale data: median recency=45d (> 30d)" And each reason includes fields {code, label, value, threshold, impact_rank} And the panel provides links/filters to view supporting items And the reasons array is available via API and exports as structured data
Organization-Level Threshold Configuration
Given an Admin sets min_N=30, dominance_threshold=70%, staleness_threshold=30d, and badge cutoffs {Low:<50, Medium:50–79, High:≥80} When they save configuration Then changes are validated, persisted, and audited with {actor, before, after, timestamp} And new thresholds apply to all workspaces within the org unless a workspace override exists And all existing themes are re-scored and re-badged within 5 minutes And non-admins cannot modify these settings (permission denied)
Manual Override with Audit and TTL
Given an Admin selects a theme X in window W and chooses Override Badge When they set badge_level = Medium with justification "Edge-case N but consistent signal" and TTL = 7 days Then the UI displays "Medium (Overridden)" with tooltip showing {actor, reason, expires_at} And the override is stored and audited with {actor, old_level, new_level, justification, ttl, created_at} And after TTL expiry or manual revert, the computed badge resumes and the audit log records the outcome And the API and exports reflect overridden=true and include override_meta
Distribution to UI, Exports, and Downstream Syncs
Given a theme with quality_score=72, badge_level=Medium, and reasons=[...] When rendering Theme List, Theme Detail, and Live Theme Map Then each view shows the same score and badge (level, color, icon) consistently And CSV exports include columns: quality_score, badge_level, data_quality_badge_color, reasons_json, overridden, override_meta And JSON exports/API include fields: quality_score (number), badge_level (enum), reasons (array<object>), overridden (bool), override_meta (object) And Jira/Linear sync payloads include quality_score and badge_level in the issue fields or description And sync responses return 2xx and pass schema validation
Bot/Noise and Anomaly Screening Effects
Given bot/noise detection flags 10% of items and anomaly screening flags an outlier on date D When scoring runs for the affected window Then flagged items are excluded or down-weighted per policy and effective N (N_eff) reflects this And if bot/noise_rate > 5% then quality_score is capped at ≤ 70 and the reasons include "High noise rate" with bot/noise_rate value And if anomaly flags are present then reasons include "Anomaly detected" with {date, type} And these effects are visible in UI, API, and exports
Sample-Aware Thresholds & Thin-Data Safeguards
"As a data-driven prioritizer, I want safeguards that prevent action on thin data so that we avoid chasing noise and committing resources prematurely."
Description

Define dynamic minimum sample and variance-based thresholds that widen confidence bands and dampen score volatility when data are thin. Show inline warnings and require user confirmation (with rationale) before pushing items with Low confidence to Jira/Linear. Provide configurable policies per workspace (e.g., block syncs below threshold, require reviewer approval) and log all overrides. Ensure safeguards propagate to ranking logic to prevent noisy items from surfacing as top priorities.

Acceptance Criteria
Dynamic Confidence Band Widening Under Thin Data
Given a workspace policy configured with min_sample_threshold=50 and variance_threshold=0.15 And a baseline confidence band defined for items meeting or exceeding thresholds When a theme’s health score is computed with sample_size < min_sample_threshold or variance >= variance_threshold Then the displayed confidence band widens relative to the baseline and never narrows as sample_size decreases or variance increases And the lower_bound and upper_bound values are persisted and exposed via API/exports And unit tests verify monotonicity across sample_size in {5,20,49,50,150} and variance in {0.05,0.15,0.30} And at sample_size <= 20 or variance >= 0.30, band width is at least 1.5x the baseline width
Volatility Dampening for Thin-Data Scores
Given a workspace policy with volatility_cap_pp=10 and smoothing_factor_min=0.6 applied when sample_size < min_sample_threshold When daily health scores are recomputed for an item with sample_size < min_sample_threshold Then the displayed score change versus the prior day is capped at 10 percentage points unless sample_size >= min_sample_threshold for 24 consecutive hours And an analytics event thin_data_dampening_applied is emitted including theme_id, pre_score, post_score, sample_size, and policy_version And a feature flag allows runtime disable/enable per workspace, verified by integration tests
Inline Warning and Rationale Prompt on Low-Confidence Sync
Given a theme with confidence_category=Low (derived from configured thresholds) When a user clicks Sync to Jira or Linear Then an inline warning explains the low-confidence status and potential risk And the Confirm Sync button remains disabled until the user provides a rationale with minimum 20 characters and checks Acknowledge low confidence And upon confirmation, the created external ticket description includes the provided rationale and a confidence summary (category, sample_size, variance) And if the user cancels, no external ticket is created and no API requests are made
Policy: Block Syncs Below Threshold
Given workspace policy sync_block_below_threshold=true And a theme has confidence below the configured threshold When a user attempts to sync the theme to Jira or Linear Then the action is blocked with a tooltip/message citing the policy and threshold values And no outbound API calls are executed And an audit entry records the attempted sync with outcome=blocked_by_policy and the evaluated confidence metrics
Policy: Reviewer Approval for Low-Confidence Syncs
Given workspace policy low_confidence_requires_approval=true and at least one reviewer configured And a theme has confidence_category=Low When a user submits a sync request Then an approval request is created, visible to reviewers with status Pending Approval And upon approval, the sync proceeds and creates the external ticket; upon rejection, the sync is not created and the requester is notified And all state transitions (requested, approved/rejected, executed) are timestamped and logged with user IDs
Override Logging and Audit Trail
Given any override action occurs (manual unblocking, rationale-confirmed sync, reviewer approval) When the action is performed Then the system logs an immutable audit record containing: user_id, workspace_id, theme_id, timestamp_utc, policy_name, prior_state, new_state, sample_size, variance, confidence_band_bounds, action, external_ticket_ids (if any) And admins can query audit records by date range, user, theme, and policy_name via API and UI And redaction rules ensure no sensitive tokens or secrets are stored
Ranking Logic Safeguard Propagation
Given workspace policy exclude_low_confidence_from_top_n=true and top_n=10 And the ranking engine computes the prioritized queue using raw score and confidence signals When a theme has a high raw score but confidence_category=Low or sample_size < min_sample_threshold Then the theme is excluded from positions 1–10 in the ranked queue And unit tests with a fixture containing one low-confidence high-score item and nine normal items verify the low-confidence item ranks below position 10 And if exclude_low_confidence_from_top_n=false, a configurable penalty is applied instead, validated by deterministic test inputs
Confidence Visualization UI
"As a product manager, I want intuitive visuals of uncertainty so that I can quickly see which items are reliable versus still forming."
Description

Render confidence bands across the live theme map, time-series views, and the ranked queue using intuitive shaded intervals or error bars. Include tooltips with N, CI bounds, confidence level, and data-quality badge plus reasons. Provide user controls to toggle bands on/off, switch confidence levels, and filter by badge. Ensure accessibility (colorblind-safe palette, keyboard navigation, ARIA labels), responsive performance on large datasets, and support for dark/light themes with visual regression tests.

Acceptance Criteria
Render Confidence Bands Across Views
Given CI bounds and health scores are available from the backend for each theme/time-bucket/queue item When the Live Theme Map, Time-Series, and Ranked Queue views render Then each view displays confidence bands (shaded intervals or error bars) aligned to the supplied CI bounds for the corresponding item And the displayed lower/upper bounds match backend values after UI rounding (absolute diff ≤ 0.01 or ≤ 0.5% of scale) And bands update within 2 seconds after a new data event is received for any affected item And no bands render for items explicitly marked as missing CI (those items display an “N/A” indicator instead)
Tooltip Details and Formatting
Given a user hovers or keyboard-focuses a data point/node/row that has a confidence band When the tooltip appears Then the tooltip contains: sample size N, CI lower bound, CI upper bound, confidence level (e.g., 95%), data-quality badge label, and up to 3 badge reasons And numeric values are formatted per UI spec (percentages to 1 decimal where applicable; integers with thousands separators) And the tooltip remains within the viewport with a 8px minimum margin and can be dismissed with Esc or on blur And screen readers announce the same content via aria-describedby for the focused element
User Controls: Toggle Bands and Confidence Level
Given a global per-view control to toggle confidence bands When the user turns bands Off Then all bands/error bars are hidden across the active view without altering base metrics/lines/scores And turning bands On restores the previously computed bands without visual drift Given a control to select confidence level with options 90%, 95%, 99% When the user changes the level Then all visible bands update to the selected level within 300 ms and widths change consistently (higher confidence => wider or equal bands) And the selected level applies uniformly across Theme Map, Time-Series, and Ranked Queue in the current session
Filter by Data-Quality Badge
Given a filter control allows selecting one or more data-quality badges (e.g., High, Medium, Low) When the user applies a badge filter Then only items matching the selected badges are visible in the Live Theme Map, Time-Series, and Ranked Queue And item counts and empty states update accordingly (showing a clear zero-state if no results) And clearing the filter restores the full dataset within 300 ms And the active filter is reflected in the view’s filter chip/state indicator
Accessibility: Color, Keyboard, and ARIA
Given colorblind-safe palettes are configured for bands, base lines/markers, and badges When evaluated against WCAG 2.1 AA Then all text/icons have contrast ≥ 4.5:1 and band vs baseline/marker remain distinguishable in protanopia/deuteranopia/tritanopia simulations Given keyboard-only navigation When the user tabs through controls and data points Then all interactive elements are reachable in a logical order, have visible focus indicators, and activate with Enter/Space; confidence level is adjustable via Arrow keys And tooltips are accessible via focus and announced with role=tooltip and aria-describedby; charts/rows have meaningful aria-labels describing value and CI
Performance on Large Datasets
Given a dataset with ~1,500 themes on the map, 50,000 total time-series points, and 1,000 ranked queue rows When the user first opens the views Then time-to-interactive (p95) is ≤ 2.0 seconds And interaction latency (p95) for hover tooltip, band toggle, confidence-level change, and badge filter is ≤ 200 ms And no main-thread long task exceeds 100 ms during these interactions (p95) And steady-state memory usage after render stays ≤ 400 MB
Dark/Light Themes and Visual Regression
Given the application supports dark and light themes When the user switches themes Then all confidence bands, base series/markers, and badge colors adapt to the theme without loss of contrast (AA compliant) And visual regression snapshots for 20 representative states across all three views in both themes pass with pixel diff ≤ 0.5% And no UI elements (text/icons/bands) become invisible or clash with background; tooltips restyle to the active theme
Sync & Export of Confidence Metadata
"As a product manager, I want confidence details to travel with the ticket so that downstream teams have the context to act appropriately."
Description

Extend Jira/Linear one-click sync and API/CSV exports to include confidence metadata: lower/upper bounds, confidence level, raw N, effective sample size, data-quality badge, and last updated timestamp. Provide field mapping to Jira custom fields and Linear metadata, with toggles to include/exclude specific elements. Maintain backward compatibility and versioned schemas, enforce permission checks, and include deep links back to EchoLens for full context.

Acceptance Criteria
Jira Sync Includes Confidence Metadata Mapping
Given Jira integration is authorized and custom field mappings exist for lower_bound, upper_bound, confidence_level, raw_n, effective_sample_size, data_quality_badge, last_updated, and deep_link When a user triggers one-click Sync to Jira for a theme or insight with Confidence Bands Then the created or updated Jira issue has each mapped field populated with the latest EchoLens values at sync time And unmapped metadata elements are not written to Jira And numeric values are stored in numeric fields, last_updated is ISO 8601 UTC, and deep_link is an absolute HTTPS URL And repeat syncing the same item updates only changed fields without creating duplicates
Linear Sync Populates Confidence Metadata
Given Linear integration is authorized and mappings are configured for Linear custom fields/metadata for lower_bound, upper_bound, confidence_level, raw_n, effective_sample_size, data_quality_badge, last_updated, and deep_link When a user triggers one-click Sync to Linear for a theme or insight Then the Linear issue is created or updated with the mapped fields populated with the latest EchoLens values at sync time And values match EchoLens at sync time and conform to the target field types in Linear And resync updates only changed fields without creating duplicates
API Export Delivers Versioned, Backward-Compatible Schema
Given a client requests the export API with schema_version=v1 When the export returns confidence metadata Then the response includes lower_bound, upper_bound, confidence_level, raw_n, effective_sample_size, data_quality_badge, last_updated, and deep_link with the v1 field names and shapes And no additional fields appear in v1 responses Given a client requests the export API with schema_version=v2 When the export returns confidence metadata Then any new fields are additive and all v1 fields remain unchanged And omitting schema_version defaults the response to v1 And the response includes a top-level schemaVersion indicator
CSV Export Includes Confidence Metadata Columns
Given a user exports items to CSV with all confidence metadata toggles enabled When the CSV is generated Then the file contains the columns: lower_bound, upper_bound, confidence_level, raw_n, effective_sample_size, data_quality_badge, last_updated, deep_link And last_updated is ISO 8601 UTC and deep_link is an absolute HTTPS URL And the number of rows equals the number of exported items and values match the API at export time Given the user disables specific elements via toggles When the CSV is generated Then the corresponding columns are omitted and remaining headers retain their order
Per-Element Include/Exclude Toggles Apply Across Channels
Given workspace-level toggles are set per element for Jira, Linear, API, and CSV When a user performs a sync or export Then only enabled elements are included for that channel and disabled elements are omitted And toggle settings persist per integration and can be overridden per export request via API parameters And default behavior includes all elements when no explicit configuration exists
Permission Enforcement on Sync and Export
Given a user lacks permission to sync/export confidence metadata or an integration token lacks the required scope When they attempt Jira/Linear sync or API/CSV export Then the action is blocked with a 403 error for API or an in-app error for UI flows, and no data is sent to external systems And authorized users or tokens can complete the same action successfully And permission checks are applied consistently across Jira, Linear, API, and CSV endpoints
Deep Links and Timestamps Are Valid and Consistent
Given an item is synchronized or exported When consumers follow the deep_link included in Jira, Linear, API, or CSV outputs Then the link resolves via HTTPS to the correct EchoLens entity without requiring additional identifiers And last_updated reflects the most recent confidence computation or metadata change at or before the sync/export time and is formatted as ISO 8601 UTC And deep_link and last_updated are present in all channels unless explicitly disabled by toggles
Evidence Gap Recommendations
"As a product manager, I want actionable steps to improve confidence so that I can reduce uncertainty and make decisions sooner."
Description

When confidence is low, generate contextual recommendations to improve signal quality, such as connecting additional data sources, targeting underrepresented segments, extending the observation window, or launching quick surveys. Estimate the projected impact on confidence (e.g., expected CI width after +X samples) and offer one-click actions to set alerts or initiate connector setup and survey templates. Send optional Slack/email nudges until confidence meets policy targets.

Acceptance Criteria
Low Confidence Triggers Evidence Gap Panel
Given a theme’s confidence score is below the workspace policy minimum OR its 95% CI half‑width exceeds the policy maximum, When a user opens the theme detail view, Then an Evidence Gap panel with a Low Data Quality badge is displayed within 1 second. Given the Evidence Gap panel is visible, When rendered, Then it displays the current sample size, current CI width, and the policy target values. Given a theme meets or exceeds the policy confidence target, When the user opens the theme detail view, Then the Evidence Gap panel and badge are not shown.
Contextual Mix of Evidence Gap Recommendations
Given missing but supported data sources are detected for the workspace, When recommendations are generated, Then at least one “Connect <Source>” recommendation is shown with source name(s) and rationale tied to historical or expected coverage. Given one or more segments have coverage < policy segment coverage threshold OR < N absolute samples (configurable), When recommendations are generated, Then a “Target underrepresented segments” recommendation lists the top underrepresented segments by name and gap size. Given recent volume is low and extending the observation window increases expected sample count by >= X per day (configurable), When recommendations are generated, Then an “Extend observation window to <W> days” recommendation is shown with the projected additional samples. Given qualitative signal is sparse (e.g., < Q text samples in last 7 days), When recommendations are generated, Then a “Launch quick survey” recommendation is shown with a preselected template. Given multiple recommendations are eligible, When the panel renders, Then duplicate or overlapping recommendations are deduplicated and capped at 5 with priority based on projected confidence impact.
Projected Confidence Impact Calculation
Given a recommendation is displayed, When the user expands its details, Then the UI shows the expected 95% CI half‑width after +X samples or after Y days, and the delta vs current (numerical and percentage). Given the backend provides a projection for CI width, When the UI renders the estimate, Then the displayed value matches the backend within max(0.01, 5%) absolute tolerance. Given variance or base rate uncertainty is too high to project reliably, When the user expands details, Then the UI shows a bounded range with a “low reliability” indicator instead of a point estimate. Given assumptions used in the projection (sample rate, variance, window), When the details are expanded, Then these inputs are visible to the user.
One‑Click Actions Execute From Panel
Given a “Connect <Source>” recommendation, When the user clicks Connect, Then the connector setup modal opens preselected to that source within 3 seconds and, upon successful setup, the recommendation state changes to In Progress and links to the new connector. Given a “Launch quick survey” recommendation, When the user clicks Create Survey, Then a survey composer opens with a prefilled template and target segment, and upon sending, the recommendation state changes to Scheduled and a confirmation toast appears. Given a “Set confidence alert” option, When the user clicks Set Alert, Then an alert form opens prefilled with the policy target and theme, and upon save, an alert is created and associated to the theme. Given any action fails due to network or server error, When the action is attempted, Then the UI shows a retriable error without duplicating side effects, and logs the failure. Given any one‑click action is triggered, When it completes, Then an analytics event is recorded with theme ID, action type, and user ID.
Automated Nudges Until Target Confidence
Given a user opts in to nudges for a low‑confidence theme, When scheduling runs, Then at most one nudge per channel (Slack/email) per theme is sent in a 7‑day period during workspace business hours. Given a nudge is sent, When the recipient views it, Then it contains current confidence, target threshold, top recommended action, and a deep link back to the theme. Given confidence meets or exceeds the policy target OR the user disables/snoozes nudges, When the scheduler runs, Then further nudges for that theme stop within 10 minutes. Given deliverability issues occur, When a nudge fails to send, Then the system retries once within 30 minutes and records the failure outcome. Given a user snoozes nudges, When the snooze period (7/14/30 days) elapses, Then nudges resume unless the confidence target has been met.
Recommendation Lifecycle and Dismissals
Given a recommendation is visible, When the user chooses Accept, Dismiss, or Snooze, Then the selected state is persisted server‑side and reflected in the UI within 2 seconds. Given a recommendation is Dismissed, When recommendations are regenerated, Then it will not reappear for 14 days unless the underlying confidence metric changes by ≥20% or the policy target changes. Given a recommendation is Snoozed, When the snooze expires or confidence worsens by ≥20%, Then the recommendation reappears in the panel. Given user actions on recommendations, When the activity log is viewed, Then entries show user, action, timestamp, and previous→new state for auditability across sessions.
Audit & Explainability Panel
"As a stakeholder, I want transparency into how confidence was computed so that I can trust the numbers and defend decisions during reviews."
Description

Provide a per-theme explainability panel that traces how each confidence band and data-quality badge was produced, including method, parameters, inputs used, recency weights, outlier handling, and thresholds applied. Show change history over time, who overrode what, and links to raw evidence. Export the audit trail for compliance and include safeguards to mask sensitive data according to workspace privacy settings.

Acceptance Criteria
Explainability Panel Opens and Summarizes Key Metrics
Given a theme with a computed confidence band and data-quality badge When a Viewer-or-higher user clicks Explain on that theme Then the Explainability Panel opens within 2 seconds And the panel displays confidence band lower and upper bounds and the confidence level (e.g., 95%) And the panel displays the data-quality badge label and numeric score And the panel displays the method name and version used for the calculation And no client or server error is logged in the last 60 seconds
Full Provenance: Methods, Parameters, Inputs, Weights, Outliers, Thresholds
Given the Explainability Panel is open for a theme When the user views the Provenance section Then the panel shows algorithm/method name and version, and all parameter names with values And it shows inputs by source type (reviews, tickets, chats, emails) with counts and the UTC time window used And it shows total sample size before and after filtering And it shows the recency weighting function name and parameters (e.g., half-life or lambda) with min/mean/max applied weights And it shows the outlier handling method (e.g., IQR, Z-score), parameter values, and the count removed And it shows thresholds applied for confidence band and data-quality badge determination And a backend recomputation for the same inputs reproduces the displayed confidence band and badge within 0.5% tolerance
Change History and Overrides Traceability
Given overrides or recalculations have occurred for a theme When the user opens the History tab Then entries are listed in reverse chronological order with ISO8601 UTC timestamp, actor, role, action type, and reason/comment And each entry shows before and after values for affected fields (e.g., parameter, threshold, badge, confidence bounds) And filters by date range, actor, and action type return only matching entries And clicking an override entry reveals who overrode what and the original system value, with rollback enabled for Admins And the history retains at least 180 days of changes And all history entries are immutable once written
Raw Evidence Links and Reproducible Sample
Given evidence contributed to the theme’s calculations When the user expands the Evidence section Then the panel lists contributing items by source with IDs, source name, timestamp, and weight And clicking an item opens the raw record in a new tab if the user has permission, otherwise a 403 message is shown And the number of listed items equals the displayed sample size after filtering (or is paginated with an accurate total) And a Reproduce Sample action returns the exact set of evidence IDs and weights used for the current computation And the evidence set obeys all active workspace filters (e.g., language, segment)
Compliance Export with Masking
Given a user with Export permission selects Export in the Explainability Panel When the user chooses JSON or CSV and a date range and confirms Then a file is generated within 10 seconds containing method/version, parameters, inputs summary, recency weights, outlier details, thresholds, confidence band bounds/level, badge details, and full change history for the range And the export includes evidence references as IDs/hashes only (no masked raw content) And the export respects workspace masking settings consistently with the UI And the export filename includes themeId and UTC timestamp And an audit log entry is created recording exporter, time, format, and range
Privacy Safeguards and Redaction Enforcement
Given workspace privacy settings define sensitive fields and patterns to mask When the Explainability Panel renders or an export is generated Then configured sensitive fields are masked in the UI and export, with a visible Masked by policy indicator And masked values are not sent to the client over the network (verified by inspecting payloads) And only Admins with Unmask permission can reveal values for the current session, with the reveal action logged And attempts to obtain unmasked values via export or API return masked values And a PII scan over a random 1,000-token sample from the panel/export yields zero unmasked matches for configured patterns

Action Rules

Create no‑code triggers (crossed threshold, rapid slope change, high‑value segment spike) that auto‑assign owners, open Jira/Linear tickets, and set SLAs. Reduces handoff lag and ensures hot themes never stall, turning health changes into immediate, accountable action.

Requirements

No‑Code Rule Builder
"As a PM or CX lead, I want to define actionable rules without writing code so that priority themes automatically create work and ownership when they change materially."
Description

Provide a visual, no‑code interface to create Action Rules composed of conditions and actions. Conditions must support theme selection, segment filters (e.g., plan tier, ARR band, geography), confidence score minimums, minimum volume, source types, and time‑windowed operators (e.g., threshold crossing, percentage spike vs. baseline, slope change). Actions must include assigning owners/teams, auto‑creating Jira/Linear issues with templated summaries/descriptions and custom field mapping, setting SLA targets, applying tags, and sending notifications. Support AND/OR logic, operator configuration, previews with historical hit counts, dry‑run/simulation, validation of incomplete/invalid rules, save as templates, and role‑based access for create/edit. Integrates with EchoLens theme map and data models to ensure rule conditions reference canonical entities.

Acceptance Criteria
Multi-Condition Rule Creation with Preview and Save
Given the user selects Theme=Checkout Failures from canonical themes And applies Segment filters Plan Tier=Pro, ARR >= $50k, Geography=NA And sets Confidence Score >= 0.70, Minimum Volume >= 20, Source Types=[Reviews, Tickets, Chats] And configures Time Window=24h and operator=Threshold Crossing at 15 mentions When the user clicks Preview Then the system returns historical hit counts for the last 30 days with daily breakdown and last hit timestamp in <= 3 seconds And the counts match underlying data for the same filters within ±1 event When the user saves the rule Then the rule is stored with a unique ID, immutable version=1, and appears in the Rule List within 2 seconds
Percentage Spike and Slope Change Operators
Given baseline window=28 days, comparison window=24h, and minimum volume threshold=15 When operator=Percentage Spike is set to >= 50% over baseline mean with z-score >= 2 Then Preview shows detection periods and calculated baseline vs observed values And a test dataset with a known 60% spike produces a hit; a control dataset without spike does not When operator=Slope Change is set to positive slope >= 3 mentions/day over the last 7 days with R^2 >= 0.6 Then Preview flags qualifying windows and yields no hit on a flat control dataset And changing sensitivity parameters (baseline length, min volume) updates Preview within 2 seconds
Boolean Logic with Nested Groups and Operator Configuration
Given the user creates Condition Group A (Theme=Latency AND Source=Tickets) and Group B (Geography=EU OR ARR > $100k) And nests Group B within Group A with overall logic A AND B When evaluating Preview on test data Then only records satisfying ((Theme=Latency AND Source=Tickets) AND (Geography=EU OR ARR > $100k)) are counted And the system supports up to 3 levels of nesting and 20 total conditions per rule And toggling a group's AND/OR recomputes Preview in < 1 second and updates the human-readable expression And parentheses are preserved in the serialized rule definition
Action Execution: Assignment, Jira/Linear Creation, SLA, Tags, Notifications
Given a rule trigger occurs When Actions include Assign Owner=pm_jane and Assign Team=Growth Then the queue item shows owner=pm_jane and team=Growth within 1 second And exactly one Jira issue is created in project=PROD with Summary/Description rendered from template tokens {{theme_name}} and {{count_24h}}, and custom fields mapped: Severity=High, SLA Target=24h And Linear issue creation behaves equivalently when Linear is selected And tags [hot, regression] are applied to the theme and created ticket And Slack channel #cx-alerts and email list cx@company.com receive notifications within 30 seconds And duplicate triggers within a 60-minute cooldown do not create additional tickets but append a comment/update And transient integration failures retry up to 3 times with exponential backoff and are visible in the rule run log
Dry-Run/Simulation Mode Over Historical Data
Given the user enables Simulation Mode and selects past 14 days When the rule is executed in simulation Then no external actions (Jira/Linear, notifications, assignments) are executed And a Simulation Report shows count of would-be triggers, timestamps, and payload previews for each action And report metrics match Preview counts for the same period within ±1 event And the user can export the simulation results as CSV and JSON
Rule Save Flow: Validation and Templates
Given required fields are missing or invalid (e.g., no theme selected, invalid threshold, unmapped custom field) When the user attempts to Save Then Save is disabled and inline validation messages identify each issue with remediation hints And once all issues are resolved, Save becomes enabled When the user saves as Template with a unique name within the workspace Then the template appears in the Template Gallery and includes conditions, actions, and operator settings And applying a template to a new rule pre-populates all fields, requiring only owner/team to be set And editing a template does not retroactively change existing rules; copying creates a new versioned template
RBAC and Canonical Entity Integration
Given roles {Admin, Editor, Viewer} When a Viewer attempts to create or edit a rule Then access is denied with a non-destructive message and the action is recorded in the audit log When an Editor creates/edits rules and an Admin manages templates and integrations Then allowed actions succeed and are recorded with actor, timestamp, and diff And the API enforces the same permissions as the UI And all rule conditions reference canonical Theme IDs and Segment dimension keys And renaming a theme in the Theme Map does not break the rule; it continues to match by ID And deleting or merging a theme prompts the user to remap affected rules and marks them as needs-attention
Real‑Time Rule Evaluation Engine
"As a product operations engineer, I want rules evaluated within a minute of data changes so that owners are notified and tickets are created while issues are still hot."
Description

Implement a scalable event processing service that ingests normalized feedback metrics and evaluates rule conditions in near real time. Support windowed aggregations and detectors including absolute threshold crossing, rate of change (slope), percentage change vs. rolling baseline, and anomaly detection (e.g., z‑score). Include hysteresis and cooldowns to prevent alert flapping, deduplication and idempotency to avoid repeated actions, backfill for late data, and ordering guarantees. Target end‑to‑end evaluation latency under 60 seconds at p95. Provide observability (metrics, traces, per‑rule execution logs), horizontal scalability, and multi‑tenant isolation. Ensure rules can reference segments and themes with current taxonomy and survive reclassification/refactoring.

Acceptance Criteria
End-to-End Latency and Horizontal Scalability SLA
Given the engine is processing 10,000 events/second across 10 tenants and 200 active rules When events are ingested, evaluated, and action events are emitted Then the p95 end-to-end latency from ingestion timestamp to action emission is <= 60 seconds for each tenant over a continuous 30-minute run And the p99 latency is <= 120 seconds And doubling worker nodes from N to 2N increases sustainable throughput by at least 1.8x while maintaining the p95 latency SLA And the metrics endpoint exposes per-tenant latency histograms and current throughput, and traces show end-to-end spans linking ingest → evaluation → action emission
Windowed Aggregations and Detector Coverage
Given rules are configured for: - Absolute threshold: count(theme='Login Errors', segment='SMB') >= 100 over 5-minute tumbling windows - Rate of change: slope(metric='NPS Detractors', window=30 minutes, step=1 minute) >= +20 units/hour - Percentage change: percent_change(metric='Refund Requests', vs rolling 7-day baseline, same weekday-hour) >= +50% - Anomaly: z_score(metric='Churn Mentions', against rolling 14-day baseline, window=15 minutes) >= 3.0 When synthetic data streams are replayed to satisfy each condition exactly once during the test period Then each corresponding rule evaluates to true exactly once and emits one action event per rule And controls where conditions are not met do not emit action events And per-rule execution logs record the evaluated window, computed values (count, slope, percent_change, z_score), and the decision outcome
Hysteresis Bands and Cooldowns Prevent Flapping
Given a threshold rule metric >= 100 with hysteresis band 10% and cooldown 30 minutes When the metric hovers between 95 and 105 for 20 minutes after an initial crossing to 101 Then exactly one action event is emitted at the first crossing And no further action events are emitted while the metric remains within the hysteresis band or cooldown is active And a new action event is only emitted after the metric falls below 90, cooldown expires, and then rises to >= 100 again And execution logs show cooldown start/end times and hysteresis thresholds used
Deduplication and Idempotent Action Emission
Given repeated evaluations of the same rule-window-key occur due to retries or partition replays When the engine processes duplicate events or replays within the same evaluation window Then at most one action event is emitted per unique idempotency key composed of {tenant_id, rule_id, theme_id, segment_key, window_start} And subsequent duplicates are acknowledged without emitting additional actions and are logged as deduplicated And downstream side effects use the same idempotency key to ensure at-most-once execution
Late Data Backfill with Ordering Guarantees
Given allowed lateness of 2 hours with event-time watermarks enabled When events arrive out of order up to 90 minutes late for a window where the rule condition was not previously met Then the window is re-evaluated upon watermark advancement, the condition is applied with complete data, and an action event is emitted if the condition becomes true And events arriving later than the allowed lateness do not alter previously finalized decisions And per-key (tenant_id, rule_id, theme_id, segment_key) evaluation respects event-time ordering across partitions
Multi-Tenant Isolation and Noisy Neighbor Protection
Given Tenant A generates 20,000 events/second with 300 rules and Tenant B generates 500 events/second with 50 rules When both workloads run concurrently for 30 minutes Then Tenant B's p95 latency and trigger rates meet the SLA and are not degraded by Tenant A's load And no data, actions, logs, or metrics for one tenant appear in the other tenant's context And resource quotas or fair scheduling prevent Tenant A from starving Tenant B evaluators
Taxonomy Refactor Resilience for Themes and Segments
Given a rule references theme_id=T1 and segment_key=S1 When the taxonomy is refactored by merging T1 into T2 and renaming S1 to S1b with a maintained mapping Then the rule continues to evaluate against the new canonical identifiers without manual edits And historical logs and audit trails preserve the original references alongside the remapped identifiers And no orphaned or broken rules are created by the taxonomy change
Jira/Linear Auto‑Ticketing & Field Mapping
"As an engineering manager, I want rule executions to open or update Jira/Linear tickets with the right context so that my team can prioritize work without manual triage."
Description

Enable automatic creation and updating of Jira and Linear issues when rules fire. Provide per‑rule configuration for project/team, issue type, priority, labels/tags, and assignee with templated fields using variables (theme, segment, counts, confidence, top examples, links). Map EchoLens attributes to Jira custom fields and Linear properties. Implement de‑duplication to update an existing open ticket for the same theme/segment instead of creating duplicates, including comment updates on re‑fires and status syncing. Support OAuth 2.0, per‑tenant credentials storage, retries with exponential backoff, error handling, and audit logs. Include a connectivity health check and sandbox mode for testing.

Acceptance Criteria
Auto-create Jira/Linear issue on rule fire with templated fields
Given an Action Rule is configured with Jira or Linear integration, target project/team, issue type, priority, labels/tags, assignee, and templated title/description using variables {{theme}}, {{segment}}, {{count}}, {{confidence}}, {{top_examples}}, and {{links}}, and connectivity is healthy When the rule fires for a specific theme and segment Then a single issue is created in the selected external system within 10 seconds with all configured fields populated and template variables resolved And the created external issue ID and URL are stored on the rule execution record and displayed in EchoLens And only configured templated/mapped fields are written; unrelated external fields remain unchanged
Per-rule field mapping to Jira custom fields and Linear properties
Given a rule-level field mapping that maps EchoLens attributes (theme, segment, count, confidence) to Jira custom fields or Linear properties with compatible types When the rule fires and creates or updates an external issue Then the mapped external fields are set to the corresponding values and pass external API validation And type conversions (number, string, label array) are applied correctly And invalid or incompatible mappings are blocked at save time with a descriptive validation error identifying the offending field
De-duplication updates existing open ticket for same theme/segment
Given an open external issue exists created by EchoLens for the same normalized theme ID and segment ID in the same target project/team and issue type When the rule fires again for that theme/segment Then no new issue is created; the existing external issue is updated instead And only templated and mapped fields are refreshed with latest values; other external fields remain unchanged And the rule execution record references the existing issue ID and is marked as "deduplicated-update" And if the external issue is in a closed/done state, a new issue is created and linked to the prior one via a relates-to link noted as "reopened by new activity"
Commenting on re-fires and status sync from external system
Given an external issue exists for a theme/segment created by EchoLens When the rule re-fires and de-duplication selects that issue Then a comment is added to the external issue containing delta metrics since last fire (new count and rate of change), the top 3 new examples, a timestamp, and a link to the EchoLens theme view And when the external issue transitions to Done/Closed, EchoLens reflects the linked queue item as Resolved within 2 minutes and records the external status and timestamp And when the external issue is Reopened, EchoLens reflects Reopened within 2 minutes
OAuth 2.0 per-tenant credentials storage and connectivity health check
Given a tenant connects Jira or Linear via OAuth 2.0 When access and refresh tokens are issued or refreshed Then tokens are stored encrypted at rest, scoped to the tenant, and never written to logs or exposed in UI And token refresh occurs automatically before expiry; failures surface a reconnect-required state to tenant admins And the connectivity health check reports Connected when a scoped API call succeeds and Disconnected with error details and last-checked timestamp when it fails And while status is Disconnected, rule executions that require external calls are not attempted and are marked Failed with a clear reason
Retry with exponential backoff, error handling, and audit logs
Given an outbound request to Jira/Linear fails with a transient condition (HTTP 429/5xx or network error) When creating or updating an issue or posting a comment Then the system retries up to 5 times with exponential backoff starting at 1 second, adding jitter and honoring Retry-After headers, before marking the attempt as Failed And the final outcome (Success or Failed) is visible in rule run details with external error code/message and a correlation ID And an audit log entry is recorded for each attempt with timestamp, tenant ID, rule ID, action type, external endpoint, request/response status and header metadata, and redaction of secrets and PII And a successful retry is explicitly labeled as such in the audit log
Sandbox mode prevents real tickets and provides preview
Given Sandbox mode is enabled for a tenant or a specific rule When the rule fires Then no external Jira/Linear resources are created or modified And a preview is generated showing resolved title, description, field mappings, target project/team, and any dedup match candidate And the execution is labeled Sandbox in logs and UI, with a link to trigger a live test after Sandbox is disabled And the sandbox run performs a harmless connectivity check and surfaces configuration errors without side effects
SLA Policy Engine & Escalations
"As a CX lead, I want SLAs applied and tracked automatically when rules fire so that critical customer issues are resolved within agreed timelines."
Description

Allow rules to set SLA targets (acknowledgment and resolution) on created work and tracked themes. Support business calendars, holidays, and time‑zone awareness, plus pause/resume on blocked states. Display SLA countdowns and status inside EchoLens and propagate targets to Jira/Linear when supported. Detect impending and actual breaches, trigger escalations (reassign, add watchers, notify Slack/Teams/email), and record breach reasons. Provide reporting on compliance by theme, segment, and owner. Ensure SLAs are versioned with the rule and preserved in audit logs.

Acceptance Criteria
Business calendar and time‑zone aware SLA calculation
Given a workspace business calendar of Mon–Fri 09:00–17:00 America/Los_Angeles with holiday 2025-11-27 and a rule defining SLAs: Acknowledge within 4 business hours; Resolve within 16 business hours When a theme is created on 2025-11-26 16:30 PST Then EchoLens sets ackDueAt to 2025-11-28 12:30 PST and resolveDueAt to 2025-12-01 16:30 PST And SLA status initializes as On track and countdown displays remaining business time When a theme is created on 2025-10-31 16:00 PDT (DST ends 2025-11-02) Then EchoLens sets ackDueAt to 2025-11-03 10:00 PST and resolveDueAt to 2025-11-04 16:00 PST And due dates are stored in UTC and rendered in the user’s local time zone without a 1‑hour drift across DST
Pause/resume SLA clocks on blocked states
Given a theme with active acknowledgment and resolution SLAs When its status changes to Blocked and the rule is configured to pause on Blocked Then both SLA clocks pause within 60 seconds, countdown labels show Paused, and no remaining time decrements while paused And an audit entry records pausedAt timestamp, actor, and reason When status changes from Blocked to In Progress Then SLA clocks resume from the exact remaining time prior to pause and dueAt values shift by the paused duration And audit log records resumedAt with actor, preserving a complete pause interval history
SLA countdown and status display in EchoLens UI
Given a theme or external ticket linked in EchoLens with ackDueAt and resolveDueAt When a user opens the detail view Then the UI shows separate acknowledgment and resolution countdowns with statuses: On track, Due soon (<20% time remaining), Paused, Breached And each timer displays absolute due date/time with time zone and updates at least every 60 seconds And a tooltip reveals basis (business calendar, holidays, policy version) When due times change from pause/resume or calendar updates Then the UI reflects the new due times within 10 seconds without page refresh
Propagate SLA targets to Jira/Linear with bidirectional sync
Given a rule that creates issues in Jira or Linear and the connector is authorized When the issue is created Then EchoLens writes acknowledgment and resolution due timestamps to supported native or custom fields and includes the SLA policy version identifier And the external issue shows matching due values within 60 seconds of creation When the external issue transitions to a mapped Blocked/On hold state Then EchoLens pauses SLA clocks internally within 60 seconds When the external issue unmapped from Blocked Then EchoLens resumes clocks and re-syncs updated due fields back to the external system And if the external system lacks SLA field support, EchoLens logs a non-blocking warning and continues internal tracking
Impending and breach detection with multi‑channel escalations
Given a rule with impending threshold set to the lesser of 10% of target or 2 hours and escalation actions: notify Slack channel, notify Teams, email owner, add watchers, and reassign on breach When an SLA becomes impending Then a single deduplicated impending alert is sent to Slack and Teams within 60 seconds and an email is sent within 5 minutes including item link, SLA type, dueAt, and time remaining And watchers are added if configured When the SLA breaches Then the item is reassigned to the configured escalation owner within 60 seconds, watchers added, Slack/Teams notifications sent, and an email issued with breach context And escalation actions are idempotent and not re-triggered more than once per stage
SLA versioning and immutable audit logging with breach reasons
Given SLA Policy Version v1 is active and v2 is published later with different targets When themes are created under v1 Then those items persist SLA Version v1 and retain v1 targets after v2 is published When new themes are created after v2 publication Then they carry SLA Version v2 targets And the audit log records rule version, author, timestamp, and change summary When an SLA breaches Then the user must select a breach reason from a configured list or enter free text; the reason, actor, and timestamp are recorded immutably in the audit log and cannot be edited without a new audit entry
SLA compliance reporting by theme, segment, and owner
Given completed items within a selected date range When a user opens SLA Reports and applies filters (theme, customer segment, owner, SLA policy version, SLA type) Then the report computes and displays: % Met for acknowledgment and resolution, average time to acknowledge/resolve, count of breaches, and trends over time And clicking a metric drills down to the underlying items And exporting CSV produces identical aggregates and item counts to the on-screen totals And report freshness is within 15 minutes of the latest event
Rule Management, Versioning, and Audit
"As an admin, I want full lifecycle management and auditing for rules so that changes are controlled, traceable, and reversible."
Description

Create a management console to list, search, filter, enable/disable, clone, and delete rules. Track versioned changes with diffs, authorship, timestamps, and rollback to previous versions. Provide execution history with outcomes and actions taken, including links to created tickets and notifications sent. Implement RBAC permissions for create/edit/approve/publish, and support staging vs. production environments with promotion workflows. Offer import/export of rule definitions as JSON and a library of starter templates. Include conflict resolution and evaluation order controls when multiple rules target the same theme/segment.

Acceptance Criteria
Rule Catalog: List, Search, Filter, Enable/Disable, Clone, Delete
- Given a user with Rule:Manage permission, When they open Rule Management, Then a paginated list shows columns: Rule Name, ID, Status (Enabled/Disabled), Environment (Staging/Prod), Owner, Last Modified, Version, Trigger Type. - Given a search query by name, ID, or description, When submitted, Then results return matching rules within 2 seconds for datasets up to 10,000 rules. - Given filters by Status, Environment, Owner, Trigger Type, Segment, and Date Modified, When applied, Then only matching rules display and active filters are shown as chips. - Given one or more selected rules, When the user toggles Enable/Disable, Then the status updates atomically and an audit entry is written per rule with user, timestamp (UTC), and action. - Given a rule, When the user clicks Clone, Then a new rule is created in Staging with identical conditions/actions, a new ID, Status=Disabled, Owner=cloner, and Version=1. - Given a rule, When the user clicks Delete and confirms, Then the rule is soft-deleted and hidden by default from the list; an audit entry is recorded; future evaluations of the deleted rule do not occur.
Versioning: Change Tracking, Diff, Authorship, Rollback
- Given a rule in Staging, When a user edits and saves changes, Then a new version number increments (e.g., v1→v2) and prior versions remain immutable/read-only. - Given any two versions, When viewing a Diff, Then changed fields for Conditions, Actions, Metadata, and Evaluation Order are highlighted with before/after values. - Given saved versions, Then each version displays Author, UTC Timestamp, and a required Change Summary entered on save. - Given a prior version selected, When the user clicks Rollback, Then a new version is created that matches the selected prior version content and becomes the current draft; an audit entry is recorded. - Given no prior versions, When attempting Rollback, Then the action is disabled with an explanatory tooltip.
Execution History and Action Artifacts
- Given any rule, When viewing Execution History, Then entries display UTC Timestamp, Environment, Inputs (Theme ID, Segment ID), Outcome (Fired/Not Fired), and Evaluation Duration in ms. - Given an execution that fired actions, Then links to created Jira/Linear tickets (external IDs) and notifications (Slack message link or Email ID) are present and open the external resource. - Given filters by Outcome, Environment, Date Range, Theme, and Segment, When applied, Then only matching executions are listed and counts update accordingly. - Given an execution entry, Then the rule Version used is shown with a link to its Diff, and each action shows Success/Failure with error message if failed.
RBAC: Create/Edit/Approve/Publish Permissions
- Given defined roles (Creator, Editor, Approver, Publisher), Then permissions are enforced as: Creator (create in Staging), Editor (edit draft in Staging), Approver (approve draft), Publisher (promote to Production). - Given a user lacking the required permission, When they attempt an action (create/edit/approve/publish), Then the system blocks it with HTTP 403 and an in-product message stating the missing permission. - Given a draft awaiting approval, When an Approver approves, Then the draft status becomes Approved and an audit entry captures approver, UTC timestamp, and version. - Given a draft not approved, When a Publisher attempts to publish, Then the Publish control is disabled with reason "Approval required." and the API returns 409 if called directly.
Environments: Staging vs Production and Promotion Workflow
- Given a new rule, Then it is created in Staging by default with Status=Disabled until explicitly enabled. - Given an Approved rule in Staging, When a Publisher promotes it, Then the exact approved version is created in Production with the specified evaluation order; promotion succeeds only if no schema conflicts exist. - Given a rule in Production, Then direct editing in Production is disabled; changes must be made in Staging and re-promoted. - Given environment separation, Then enabling/disabling a rule is environment-specific and does not affect the other environment. - Given any promotion, Then an audit record includes source rule/version, target environment, initiator, and UTC timestamp.
Import/Export of Rules and Template Library
- Given a valid JSON rule definition, When imported into Staging, Then the rule is created as Disabled with Version=1; on schema validation failure, import is rejected with line/field errors. - Given an import where incoming IDs collide with existing rules, Then new IDs are assigned and references remapped to avoid collisions. - Given one or more selected rules, When exported, Then a JSON file is generated containing complete definitions including versions and evaluation order. - Given a Template Library, When a user previews a template, Then they can instantiate it into Staging with prefilled conditions/actions and editable fields; the new rule is Disabled by default.
Conflict Resolution and Evaluation Order Controls
- Given multiple rules target the same Theme/Segment, Then evaluation occurs in ascending Priority order (1=highest); ties are broken by earlier Created Timestamp. - Given conflicting actions on the same Theme/Segment within the same evaluation cycle (e.g., different SLAs), Then only the highest-priority rule's conflicting action is applied; lower-priority conflicting actions are skipped and annotated in Execution History. - Given the Rule Management console, When a user updates a rule's Priority via drag-and-drop or numeric input, Then the change persists, is versioned in Staging, and takes effect in Production only after promotion. - Given evaluation order changes, Then Diffs display before/after positions for affected rules and an audit entry records the change.
Targeted Notifications & Routing
"As a team lead, I want targeted, low‑noise notifications when rules fire so that the right people act quickly without being overwhelmed."
Description

Deliver notifications when rules fire to Slack, Microsoft Teams, email, and webhooks with per‑rule routing by owner, team, severity, and segment. Support message templates with variables, batching and digest modes, rate limiting and quiet hours to reduce alert fatigue, retries with backoff, delivery status tracking, and unsubscribe preferences. Integrate with on‑call platforms (PagerDuty/Opsgenie) as optional endpoints. Provide channel health checks and per‑tenant throttling to avoid abuse and ensure reliability.

Acceptance Criteria
Per‑Rule Multi‑Channel Routing
Given a rule with routing mappings by owner, team, severity, and segment to Slack, Microsoft Teams, email, and webhook destinations When an event matches the rule attributes (e.g., owner=oncall-gx, team=Growth, severity=High, segment=Enterprise) Then notifications are sent exactly once to each configured destination for that attribute set within 5 seconds of rule firing And each notification payload includes rule_id, event_id, theme_id, severity, segment, confidence_score, triggered_at, and deep_link to EchoLens And no destinations outside the configured mapping receive the notification And duplicate suppression prevents more than one notification per destination per event_id
On‑Call Platform Endpoints (PagerDuty/Opsgenie)
Given PagerDuty and/or Opsgenie endpoints are configured for severity=Critical rules with valid credentials When a Critical event fires that matches the rule Then a PagerDuty incident OR Opsgenie alert (for the endpoints enabled) is created with summary, details, and routing key derived from the rule’s message template And a deterministic dedup_key is used per event_id to avoid duplicate incidents/alerts on retries And acknowledge/resolve callbacks from the on‑call platform update the notification status in EchoLens within 15 seconds And if both platforms are enabled, both receive alerts independently
Message Templates with Variables & Previews
Given a rule uses a message template with variables {{rule_name}}, {{theme_name}}, {{severity}}, {{segment}}, {{confidence}}, {{count}}, {{link}}, and custom metadata When the rule fires Then the message renders all provided variables correctly and escapes/strips unsafe content And variables missing in the event context use configured fallbacks or are omitted without breaking formatting And template validation blocks saving templates with syntax errors or unknown variables, returning a clear error And the user can preview the resolved message with sample data before saving
Batching and Digest Modes
Given batching is enabled with a 10‑minute window and max_items=20 per message When multiple matching events occur within the batch window Then a single batched notification per destination is sent containing total_count, top_items (<=20), and an overflow indicator when applicable And items are grouped by theme and ordered by severity desc then confidence desc And daily/weekly digest mode aggregates events over the configured period and sends at the scheduled time respecting the same limits And deduplication prevents the same event_id from appearing in multiple batches for the same window
Rate Limiting, Quiet Hours, and Per‑Tenant Throttling
Given per‑destination rate limits (e.g., 10 messages/min), quiet hours (e.g., 22:00–07:00 in tenant timezone), and per‑tenant throughput caps (e.g., 100 messages/min) are configured When events exceed a destination’s rate limit Then excess notifications are queued and coalesced into batches without exceeding limits And during quiet hours, real‑time notifications are suppressed and a single summary is sent at quiet‑hours end unless overridden by severity>=Critical And per‑tenant caps are enforced across all destinations to prevent abuse, with dropped or deferred counts recorded in metrics And no recipient receives more than the configured max within the sliding window
Retries with Exponential Backoff & Dead‑Letter Handling
Given a notification delivery attempt fails with a retryable error (network timeout or 5xx) When retries are executed Then exponential backoff with jitter is applied (e.g., 2s, 4s, 8s…) up to max_attempts, not exceeding max_retry_duration And non‑retryable errors (4xx except 429) are not retried And after exhausting retries, the notification is moved to a dead‑letter queue with error_code, last_response, destination, and next_steps recorded And idempotency keys prevent duplicate deliveries on retries for endpoints that support deduplication
Delivery Status, Unsubscribe Preferences, and Channel Health Checks
Given each notification is assigned a UUID and tracked across lifecycle states When deliveries occur Then status transitions (queued, sent, delivered, failed, suppressed, dead_lettered) are persisted with timestamps and visible in UI/API for at least 30 days And per‑destination health checks (manual “Test connection” and scheduled heartbeat) report healthy/unhealthy within 3 seconds and block sends to unhealthy destinations with automatic recheck And users can set unsubscribe preferences by channel, severity, team, and segment; recipients matching an unsubscribe rule are excluded while team‑level destinations still receive messages And email notifications include a one‑click unsubscribe link scoped to that rule/channel combination And fallback routing (if configured) is used when a destination is unhealthy or unsubscribed, with the reason logged
Theme Map Annotations & Backlinks
"As a product manager, I want active rules and tickets visible on the theme map so that I can assess impact and progress at a glance."
Description

Annotate the live EchoLens theme map when rules fire, showing badges for active alerts, SLA status, and linked tickets on affected nodes. Enable drill‑down from a node to the triggering rule, associated evidence, and created work. Allow filtering the map by rule, severity, owner, and SLA state to visualize operational focus. Maintain backlinking from Jira/Linear issues to the corresponding EchoLens theme and rule execution for context. Ensure real‑time updates and efficient rendering at scale.

Acceptance Criteria
Active Alert Badges on Theme Map Nodes
Given a rule fires for a theme node with severity and SLA, When the map receives the real-time event or refreshes, Then the node displays an Active Alert badge with severity color, SLA countdown, and linked ticket count. Given multiple rules affect the same node, When badges render, Then badges stack with a numeric counter and a tooltip listing rule names and severities. Given the triggering ticket is resolved or the rule condition no longer holds, When the system processes the update, Then the Active Alert badge is removed within 5 seconds and the node shows no active alert state. Given the user pans or zooms the map, When badges redraw, Then badges remain anchored to their nodes and do not overlap node labels (minimum 8px spacing).
Drill-Down to Triggering Rule, Evidence, and Work
Given a node has an active or recent rule execution, When a user clicks the node, Then a drill-down opens within 500 ms showing rule name, rule ID, execution timestamp, and triggering metric values (threshold, rate of change, segment). Given the rule execution has evidence items, When the drill-down loads, Then at least the 5 most recent evidence items display with source type, excerpt, timestamp, and a link to the full item, with pagination available. Given the rule created Jira/Linear tickets, When the drill-down loads, Then each ticket shows system, key/ID, title, assignee, status, and SLA due, and clicking opens the ticket in a new tab. Given the user lacks permission to view certain evidence, When the drill-down loads, Then those items are redacted and a message indicates restricted content.
Map Filtering by Rule, Severity, Owner, and SLA State
Given the map is visible, When a user applies filters for rule (multi-select), severity (multi-select), owner (multi-select), and SLA state (On Track, At Risk, Breached), Then only nodes matching all selected filters are highlighted and non-matching nodes dim to 30% opacity. Given no filters are applied, When the map loads, Then all nodes render at normal opacity with zero filters indicated. Given filters are applied, When the user reloads or shares a URL, Then the filter state is preserved via URL parameters and restored on load. Given up to 10,000 nodes and 5,000 active annotations, When filters change, Then results update within 300 ms at P95 and 800 ms at P99. Given a filter yields no matches, When applied, Then the UI shows 0 matches and provides a one-click Clear Filters action.
Backlinks from Jira/Linear to EchoLens Theme and Rule Execution
Given a ticket is created by an Action Rule, When it is created in Jira/Linear, Then it includes a backlink to the EchoLens theme and specific rule execution (deep link with themeId and executionId) in a dedicated field or link section. Given a ticket was created without a backlink due to an API error, When the retry worker runs, Then the backlink is added within 2 minutes using up to 3 exponential backoff attempts and failures are logged and surfaced to admins. Given a ticket is updated in Jira/Linear (status, assignee, title), When the change occurs, Then the EchoLens drill-down reflects the new metadata within 10 seconds without breaking the backlink. Given a theme is merged or split in EchoLens, When a backlink is followed from a ticket, Then it resolves to the canonical theme and presents merge/split context to the user.
Real-Time Badge and SLA Status Updates
Given a rule state, ticket status, or SLA state changes, When the event is emitted, Then the corresponding node badge and SLA indicator update on the map within 5 seconds via WebSocket and within 15 seconds via polling fallback. Given the real-time connection is lost, When the client detects a disconnect, Then a connection status indicator is shown and polling at 10-second intervals is enabled until reconnection, after which WebSocket resumes. Given multiple updates arrive within 1 second for the same node, When rendering occurs, Then updates are coalesced to a single redraw without flicker and the final state matches the latest event.
Rendering Performance and Stability at Scale
Given 10,000 nodes, 50,000 edges, and up to 5,000 active annotations, When the map renders, Then pan/zoom maintains at least 45 FPS on a 2019+ laptop with integrated graphics and tab memory stays under 500 MB. Given a user rapidly pans and zooms for 10 seconds, When interaction stops, Then no visual artifacts or overlapping badges persist and CPU usage falls below 30% within 2 seconds of idle. Given a sustained stream of 1,000 badge updates per minute for 5 minutes, When processing events, Then no client errors occur and the system demonstrates at-least-once delivery with 0 dropped updates.
Permissions, Redaction, and Audit Trails in Drill-Down
Given an organization role restricts evidence access, When a user opens a node drill-down, Then restricted evidence snippets are redacted with a Restricted label while badge and ticket metadata remain visible. Given a user without ticket access attempts to open a linked Jira/Linear ticket from EchoLens, When the click occurs, Then the user sees an access denied message and the attempt is logged. Given an annotation or backlink is created, updated, or removed, When the event occurs, Then an audit log entry records actor, timestamp, node ID, rule ID, action, and outcome, retrievable via the admin console.

Glidepath Forecast

Short‑term projections show where a theme’s health is likely headed over the next 7–30 days based on momentum and decay. Distinguish self‑healing noise from brewing fires so teams can plan fixes, comms, and capacity with fewer surprises.

Requirements

Theme Health Index
"As a product manager, I want a single, reliable health score for each theme so that I can quickly compare themes over time and decide where to focus."
Description

Define and compute a normalized 0–100 health score per theme that blends volume velocity, sentiment trend, severity tags, and customer value weighting across all ingested sources (reviews, emails, tickets, chats). Apply recency decay, deduplication of near-duplicate messages, and source weighting to prevent bias. Handle missing data and low-signal themes with smoothing and guardrails. Persist score history for time-series use and expose through internal APIs for UI, forecasting, and sync integrations.

Acceptance Criteria
Normalized Score Calculation Across Signals
- Given a theme with 200 messages in the last 7 days (velocity +40% vs prior 7 days), sentiment trend = -0.15, 35% tagged High severity, and customer value in the top quartile, When the health index is computed, Then the score is within [0,100] and is ≥10 points lower than the same theme computed with neutral sentiment (0.00) and 0% High severity. - Given any valid input, When the health index is computed, Then the output includes a components breakdown (volume, sentiment, severity, value) whose normalized contributions sum to 1.0 ± 0.01 and the final score reflects these weights. - Given any input combination, When the health index is computed, Then no NaN/null/∞ values are returned and all missing signals are handled per missing-data rules.
Recency Decay Dominates Recent Signals
- Given two identical sets of messages (100 in last 7 days; 100 in days 31–60) with the same sentiment/severity, When recency decay is applied, Then the effective weight of the last-7-days set is ≥10x the 31–60-day set and messages >30 days contribute ≤5% of total weight. - Given only messages older than 60 days and no new data, When recomputed daily, Then the score converges toward the low-signal baseline within 14 days and day-over-day change does not exceed 2 points without new data.
Near-Duplicate Deduplication
- Given 50 messages with ≥0.90 text similarity under the same theme and account within 24h, When deduplication runs, Then they collapse to a single canonical message for scoring and the effective volume increases by 1. - Given two different accounts posting ≥0.90 similar messages under the same theme, When clustering runs, Then they count as two unique signals for scoring. - Given deduplication is applied, When the score is computed, Then an auditable cluster size and representative IDs are recorded for the computation window.
Source Weighting Prevents Single-Source Bias
- Given inputs from chats (900), tickets (90), emails (9), and reviews (9) with similar signal distributions, When source weighting is applied, Then no single source contributes >50% of the composite score weight and configured per-source weights are returned in the breakdown. - Given only one source is available for a theme, When the score is computed, Then the single-source condition is flagged and the score still reflects that source while respecting the bias cap.
Missing Data Smoothing and Low-Signal Guardrails
- Given a theme with <5 unique messages in the last 7 days, When the score is computed, Then the lowSignal flag is true and day-over-day score change is clamped to ±5 points. - Given sentiment coverage = 0% for the scoring window, When the score is computed, Then sentiment is imputed to neutral (0.0) and sentimentCoverage=0 is returned in the breakdown. - Given any missing signal (sentiment, severity, or value), When the score is computed, Then a confidence value in [0,1] is returned that decreases with lower coverage and smaller effective sample size.
Score History Persistence and Internal API Exposure
- Given daily computation at 00:15 UTC, When persisted, Then a record per theme is stored with date, score (0–100), components breakdown, lowSignal flag, and algorithm version, with retention ≥365 days. - Given GET /internal/themes/{id}/health-index?range=30d, When called, Then the API returns 30 daily points in chronological order within 300ms p50 and 1s p95 for up to 100 themes batched. - Given POST /internal/recompute/health-index?themeId={id}&range=90d, When invoked, Then the last 90 days are recomputed, versioned, and audit-logged, and prior versions remain queryable via a version parameter for 7 days.
Short‑Term Glidepath Forecaster
"As a CX lead, I want short‑term projections for each theme so that I can plan capacity and interventions before issues spike."
Description

Build a short‑horizon (7–30 day) forecasting service that predicts each theme’s Health Index trajectory using momentum and decay signals. Implement an algorithmic approach (e.g., exponential smoothing with recency-weighted inputs and optional seasonality handling) that outputs point forecasts and prediction intervals, refreshes on a fixed cadence (e.g., hourly/daily), and gracefully handles cold starts via heuristic baselines. Provide APIs returning 7, 14, and 30‑day projections, interval bounds, last update time, and model version metadata.

Acceptance Criteria
API returns 7/14/30-day forecasts with required metadata
Given a valid themeId with at least 30 days of historical Health Index values When the client requests forecasts via GET /themes/{themeId}/glidepath Then the response status is 200 And the response body contains: horizons {7:{point,lower,upper},14:{point,lower,upper},30:{point,lower,upper}}, lastUpdateTime (ISO 8601 UTC), and modelVersion (semver) And for each horizon, lower <= point <= upper And all numeric values are within [0,100] with at most 2 decimal places And lastUpdateTime is no older than one configured refresh cadence interval from current time
Forecast refresh cadence meets SLO
Given the forecast refresh cadence is configured to hourly When the top of the hour occurs Then forecasts for all active themes are recomputed and persisted within 10 minutes And each theme’s lastUpdateTime is strictly greater than the previous run’s lastUpdateTime And if no new source data arrived since the last run, the recomputed point forecasts differ by no more than 0.01 from the prior values
Cold start fallback produces baseline forecasts
Given a theme has fewer than 10 historical daily Health Index observations in the last 30 days When a forecast is requested Then the service returns point forecasts equal to the recency-weighted mean of available observations (exponential decay with half-life = 7 days) And for each horizon, the prediction interval width is at least 20.0 index points after clipping to [0,100] And the response includes horizons for 7, 14, and 30 days
Prediction intervals are coverage-calibrated
Given a rolling backtest over the last 30 days on themes with at least 60 days of history When computing empirical coverage of the served 80% prediction intervals for horizons 7, 14, and 30 days Then the empirical coverage lies between 75% and 85% for each horizon And the mean absolute error (MAE) of point forecasts is ≤ 8.0 index points for each horizon
Seasonality handling improves accuracy when weekly seasonality is present
Given a theme exhibits statistically significant weekly seasonality (autocorrelation at lag 7 days ≥ 0.30 with p < 0.05) When generating forecasts with optional weekly seasonality enabled Then backtested MAE improves by at least 10% versus the same model without seasonality for horizons 7, 14, and 30 days And if the seasonality criterion is not met, seasonality is not applied
API contract and error handling
Given an invalid themeId When the client requests GET /themes/{themeId}/glidepath Then the response is 404 with error.code = "THEME_NOT_FOUND" and a JSON body matching the published error schema Given an unsupported horizon parameter When the client requests GET /themes/{themeId}/glidepath?horizon=999 Then the response is 400 with error.code = "INVALID_HORIZON" Given an internal processing failure When the client requests forecasts Then the response is 503 with error.code = "FORECAST_UNAVAILABLE"
Performance and scalability SLOs
Given normal operating conditions When the client calls GET /themes/{themeId}/glidepath Then p95 latency is ≤ 300 ms and p99 latency is ≤ 800 ms over a 5-minute window at 50 RPS Given an hourly refresh for 50k active themes When the scheduled refresh job runs Then it completes within 15 minutes And average CPU utilization during the job is ≤ 70% And peak memory usage remains ≤ 80% of allocated capacity
Noise‑vs‑Fire Classifier
"As a triage lead, I want to know which themes are likely to self‑resolve versus escalate so that I can avoid over‑allocating attention to noise."
Description

Create a classifier that labels themes as likely self‑healing noise or brewing fire using forecast slope, interval width, recent sentiment acceleration, and change in high‑value customer mentions. Define thresholds and hysteresis to reduce flip‑flopping, and backtest on historical data to target precision/recall goals. Output a label, confidence score, and recommended action (monitor, inform, escalate). Integrate the label into queues and filters to focus triage on high‑risk themes.

Acceptance Criteria
Label and Confidence Computation
- Given default thresholds are configured (fire_cutoff=0.60), a 7-day evaluation window, and signal normalization against a 90-day baseline; When the classifier evaluates forecast slope, forecast interval width, recent sentiment acceleration, and change in high-value customer mentions; Then it returns exactly: label in {"Noise","Fire"}, confidence in [0,1] with two-decimal precision, and recommended action in {"Monitor","Inform","Escalate"} consistent with bands (Monitor <0.60, Inform 0.60–0.84, Escalate ≥0.85). - Given a test fixture where the composite score ≥ 0.60; When evaluated; Then label = "Fire" and confidence ≥ 0.60. - Given a test fixture where the composite score < 0.60; When evaluated; Then label = "Noise" and confidence < 0.60. - Given identical inputs across two runs; When evaluated; Then outputs (label, confidence, action) are bit-for-bit identical and timestamped with the evaluation window end time.
Hysteresis to Reduce Flip-Flopping
- Given hysteresis band h = 0.10 around fire_cutoff and a dwell time of 24h; When confidence oscillates within [0.50, 0.70] after a recent label; Then the prior label is retained until confidence exits the band for ≥ 24h or crosses ≥ 0.70 or ≤ 0.50. - Given a sequence that crosses above 0.70 from below; When evaluated; Then the label switches to "Fire" immediately and persists unless confidence drops ≤ 0.50 for ≥ 24h. - Given backtest over 90 days; When comparing with a no-hysteresis baseline; Then per-theme label toggles are reduced by ≥ 50% and do not exceed 1 toggle per 48h on average.
Backtest Precision/Recall Targets
- Given a 120-day historical dataset with time-ordered splits and no leakage; When evaluated with 5-fold rolling-origin CV at default thresholds; Then Fire precision ≥ 0.75, Fire recall ≥ 0.65, and PR-AUC ≥ 0.70 overall. - Given confidence bins [0.85–1.00] and [0.60–0.84]; When calibrated; Then empirical Fire rate in the bins is ≥ 0.80 and ≥ 0.60 respectively, with 95% bootstrap CI width ≤ 0.06. - Given segmentation by source (reviews, emails, tickets, chats) and customer tier; When reporting; Then metrics per segment meet at least precision ≥ 0.70 and recall ≥ 0.60 or are flagged in the report for threshold tuning.
Action Mapping
- Given label = "Fire" and confidence ≥ 0.85; When mapping to actions; Then recommended action = "Escalate" with default SLA hint ≤ 24h. - Given label = "Fire" and 0.60 ≤ confidence < 0.85; When mapping to actions; Then recommended action = "Inform". - Given label = "Noise" and confidence < 0.60; When mapping to actions; Then recommended action = "Monitor". - Given any mapped action; When rendered in API and UI; Then action value exactly matches one of {"Monitor","Inform","Escalate"}.
Queue, Filter, and Sync Integration
- Given the ranked theme queue; When a theme is labeled "Fire"; Then a visible badge shows the label and confidence, and the default sort places Fire above Noise when impact scores tie. - Given filter controls; When "High Risk" is selected; Then only Fire-labeled themes are shown; When "Noise Only" is selected; Then only Noise-labeled themes are shown. - Given one-click Jira/Linear sync; When a theme is pushed; Then fields classifier_label, classifier_confidence, and recommended_action are present in the ticket/task payload and match EchoLens values.
Update Freshness and Recompute Latency
- Given new tickets/chats/emails are ingested; When they affect a theme; Then the classifier output is recomputed and visible in the queue and live theme map within 5 minutes (P95) and 10 minutes (P99). - Given new app store reviews are ingested; When they affect a theme; Then recompute completes within 15 minutes (P95) and 30 minutes (P99). - Given no new data; When the periodic job runs; Then the classifier re-evaluates themes at least every 10 minutes and timestamps reflect the latest evaluation.
Forecast Confidence & Explainability
"As a product manager, I want to understand why a forecast looks a certain way so that I can trust it and communicate the rationale to my team."
Description

Calculate and expose a per‑theme forecast confidence score derived from residual error, data sufficiency, and model agreement. Provide top drivers that explain the trajectory (e.g., rising negative sentiment, spike in ticket volume from enterprise accounts) and show contribution magnitudes. Store diagnostics for auditability and display concise explanations in the UI and API payloads to build trust and aid stakeholder communication.

Acceptance Criteria
API returns per‑theme forecast confidence with components
Given a theme with ≥30 days of signals and a forecast computed within the last 6 hours When a client calls GET /v1/themes/{theme_id}/glidepath Then the response includes confidence.score as a float between 0.00 and 1.00 with two decimal precision And includes confidence.label in {"low","medium","high"} mapped to [0.00–0.33), [0.33–0.66), [0.66–1.00] And includes confidence.components with keys {residual_error, data_sufficiency, model_agreement} each in [0,1] and weights summing to 1.00 ± 0.01 And includes explanation.drivers[1..5] each with {key, direction in {"up","down"}, contribution (float, signed), unit in {"pct","count"}, source in {"reviews","emails","tickets","chats"}} And the absolute sum of driver.contribution predicts the 7–30d projected magnitude within ±10% And includes forecast_id and trace_id as UUIDv4 values
UI confidence badge and tooltip explanation
Given a theme detail page with a computed Glidepath forecast When the user opens the Forecast section Then a confidence badge is visible with text {High|Medium|Low} matching the API confidence.label mapping And hover or keyboard focus on the badge shows a tooltip within 200 ms listing the top 3 drivers with icons for direction and signed contribution magnitudes with units And the tooltip content is concise (≤240 characters), does not overflow its container, and is dismissible via Esc and blur And the badge and tooltip are keyboard reachable and have aria-labels to meet WCAG 2.1 AA
Top drivers ranking and contribution rules
Given driver candidates from multiple sources and segments When the top drivers are computed Then the list contains at most 5 drivers ranked by absolute contribution descending And drivers with absolute contribution <3% are omitted And each driver includes segment qualifiers when applicable (e.g., account_tier="enterprise") in its key And direction reflects the driver metric movement ("up"/"down") and contribution sign reflects impact on forecast (positive=improving, negative=worsening) And the normalized absolute contributions sum to 1.00 ± 0.05
Diagnostics persistence and retrieval for auditability
Given a forecast is generated for a theme When diagnostics are stored Then a record is written containing {forecast_id, trace_id, created_at, model_versions[], residual_metrics{MAE, MAPE, RMSE}, fit_window_days, training_sample_size, data_coverage_pct, component_scores, driver_list} And records are immutable, retained ≥180 days, and contain no PII (no message bodies, emails, or user identifiers) And GET /v1/forecasts/{forecast_id}/diagnostics by an authorized org member returns 200 with the record; unauthorized returns 403; nonexistent returns 404 And the diagnostic payload size is ≤200 KB on average and ≤1 MB max
Model agreement computation and effect on confidence
Given an ensemble of three forecasters is configured for Glidepath When a forecast is generated Then model_agreement is computed in [0,1] as 1 − normalized RMSE among member predictions over the 7–30 day horizon and returned in confidence.components.model_agreement And if model_agreement <0.30, confidence.score is reduced by ≥0.20 post-weighting and confidence.label cannot be "high" And the agreement value and computation method identifier are stored in diagnostics
Data sufficiency gating and fallback behavior
Given a theme with <15 distinct days of data in the last 45 days or data_coverage_pct <60% When a forecast is requested Then the API returns data_sufficiency ≤0.30 in confidence.components and confidence.label="low"; if <7 days, status="insufficient_data" and no projection series is returned And no drivers are returned when status="insufficient_data" And when data meets thresholds (≥30 days and coverage ≥80%), data_sufficiency ≥0.70
Theme Map Visualization with Projection Cone
"As a user, I want an at‑a‑glance visual of where each theme is headed so that I can prioritize the most urgent issues quickly."
Description

Integrate glidepath visuals across the product: sparkline plus projection cone on theme cards and a detailed chart in theme detail pages. Indicate predicted direction (rising/stable/falling), show 7/30‑day markers, and color‑code by risk level. Support responsive layouts, keyboard navigation, and accessible contrast. Add hover/tooltips for interval values and driver summaries. Ensure real‑time updates align with existing live theme map refresh cycles.

Acceptance Criteria
Theme Card: Sparkline and Projection Cone
Given a theme has ≥14 days of historical daily values and forecast quantiles p10/p50/p90 for the next 30 days When its theme card is rendered in the Theme Map Then a sparkline displays the last 14 days of actual values And a projection cone displays days +1 to +30 with p10–p90 shaded band and a p50 line And 7d and 30d markers are visible at the corresponding projected dates And hovering or focusing the card shows a mini-tooltip with next 7d projected p50 and p10–p90 range And initial render introduces no layout shift greater than CLS 0.05
Theme Detail: Forecast Chart with Tooltips
Given the user opens a theme detail page with last 30 days of actuals and next 30 days of forecast quantiles p10/p50/p90 When the forecast chart loads Then the chart displays actuals for the prior 30 days and a projected p50 line with a shaded p10–p90 cone for the next 30 days And vertical markers labeled 7d and 30d are present on the projection timeline And on hover/focus at any point, a tooltip shows date, actual/projection p50, lower p10, upper p90 And the tooltip includes the top 3 driver summaries for that interval with count and percent share And tooltip opens within 100 ms and positions without occluding the hovered point
Risk Level Color-Coding and Direction Badge
Given a theme has risk_level in {low, medium, high} and delta = (avg(next 7d p50) − avg(last 7d actual)) / avg(last 7d actual) When the card and detail header render Then the risk pill colors are: low #16A34A, medium #F59E0B, high #DC2626 with text contrast ≥ 4.5:1 And the direction badge shows Rising if delta ≥ +5%, Stable if −5% < delta < +5%, Falling if delta ≤ −5% And the badge icon matches the state (up/flat/down) And risk and direction are conveyed via text/aria-labels, not color alone
Responsive Layouts for Cards and Charts
Given viewport ≥ 1200px When rendering theme cards Then each card shows sparkline, cone, 7d/30d markers with text labels, and risk/direction badges, with ≥ 4 cards per row and no horizontal scroll Given viewport 768–1199px When rendering Then markers appear as ticks without text labels, tooltips provide labels, and ≥ 2 cards per row with no horizontal scroll Given viewport < 768px When rendering Then sparkline+cone height is 48 px, markers are ticks, labels hidden, tap/long-press reveals tooltip, and all tappable targets are ≥ 44×44 px with no horizontal scroll
Keyboard Navigation and Screen Reader Support
Given focus is on a theme card When the user presses Enter Then the card opens its detail or expands per configuration and focus remains within the chart container Given focus is on a chart container When Arrow Left/Right are pressed Then the crosshair moves by 1 day between points and updates tooltip content And Tab/Shift+Tab cycle through risk pill, direction badge, 7d marker, and 30d marker in order And Escape closes any open tooltip And all focus indicators are visible with ≥ 3:1 contrast against the background And the chart has role="img" and an aria-label summarizing direction, risk level, and next-7d projection
Live Update Refresh and State Preservation
Given the Live Theme Map emits a refresh event on its standard interval When a refresh event is received Then sparkline and projection cone data update within 1 second And any open tooltip remains open and its values refresh to the latest data And the user’s scroll position and keyboard focus are preserved And visual changes animate in ≤ 300 ms without flicker And p95 CPU time for the visual update is ≤ 50 ms per 100 cards
No-Data and Error States Handling
Given a theme has < 3 days of history or no forecast available When rendering the card and detail chart Then the projection cone and direction badge are hidden And a neutral message "Insufficient data for forecast" is displayed in #6B7280 And the risk pill shows "N/A" with neutral styling And tooltips are disabled for future dates Given a data fetch error occurs When the UI renders Then an error badge appears with a retry action and no stale projections are shown
Jira/Linear Sync with Forecast Signals
"As a product manager, I want forecast details included when I create tickets so that engineering understands urgency and timing without switching tools."
Description

Augment one‑click sync to Jira/Linear by attaching forecast context: current Health Index, 7/14/30‑day projections, prediction interval, Noise‑vs‑Fire label, and recommended priority/due date. Map fields to destination custom fields, support project‑level defaults, and ensure idempotent updates on re‑sync. Respect permissions and redact sensitive content. Log sync payloads and outcomes for traceability.

Acceptance Criteria
Attach forecast signals on one-click sync to Jira/Linear
Given a theme has forecast data: Health Index=0.73, 7/14/30-day projections=18/32/61, prediction interval=[0.65,0.80], Noise-vs-Fire=Fire, recommended priority=High, recommended due date=2025-08-24 And a destination (Jira or Linear) and field mappings are configured When the user clicks Sync from the theme in EchoLens Then exactly one issue is created in the selected destination project/team And the mapped fields are populated with the exact values present at sync time And numeric fields are stored with correct types and precision (Health Index rounded to 2 decimals; projections as integers) And the prediction interval is stored per mapping (single "low–high" text or two numeric fields) And the destination issue is linked back to the theme with a persistent association
Field mapping configuration and project-level defaults honored
Given project-level defaults exist for destination project/team, issue type, assignee, and custom field mappings And no per-sync overrides are set When a user syncs a theme Then the defaults are applied to the created destination issue And if per-sync overrides are provided, those override defaults for that sync only And changes to project-level defaults apply to subsequent syncs without affecting already-synced issues And required mappings are validated before sync, with a clear error if any required mapping is missing
Idempotent re-sync updates without duplicates
Given a theme has an existing linked destination issue When the user triggers Sync again Then no new destination issue is created And only mapped forecast fields that differ from the destination are updated And no duplicate comments, labels, or links are created And the sync record shows "upsert" with the updated fields list And if no values changed, the sync outcome is "no-op"
Permission checks and sensitive content redaction
Given the user's destination credentials lack create or edit permissions When the user attempts to sync Then the sync is prevented with a clear error indicating the missing permission scope And no payload is sent to the destination if create permission is missing Given redaction rules for PII are enabled And the theme contains emails, phone numbers, or tokens in text fields When syncing Then those values are masked in both the outbound payload and stored logs And the destination issue contains only redacted text
Logging and traceability of sync payloads and outcomes
Given a sync is performed When viewing the theme’s Sync History Then the record contains timestamp, user, destination, request payload (redacted), response status code, destination issue key/ID, and correlation ID And failures include the error code and message from the destination And logs are retained for at least 90 days and are exportable as JSON And logs are filterable by theme, destination, status, and date range
Noise-vs-Fire and priority/due date mapping applied
Given Noise-vs-Fire=Fire And recommended priority=High and recommended due date=2025-08-24 When syncing to a destination with mapped priority, label/tag, and due date fields Then the destination priority is set to the mapped value for High And the due date is set to 2025-08-24 in the destination project timezone And the Noise-vs-Fire value is applied to the configured destination label/tag field And on re-sync, the mapped priority and due date update to the latest recommendation
Validation and graceful handling of mapping/type mismatches
Given a mapped destination custom field has an incompatible type (e.g., text field mapped to numeric Health Index) When syncing Then the sync proceeds without failing the whole operation And compatible fields are updated And the outcome records a field-level validation error naming the field and expected type And the user is prompted to fix the mapping before the next sync
Backtesting, Monitoring & Guardrails
"As a product owner, I want automated accuracy monitoring and safeguards so that forecasts remain reliable in production and we can respond quickly to degradation."
Description

Implement nightly backtests (e.g., rolling origin) to measure MAPE/RMSE and calibration of prediction intervals per theme cohort. Monitor data drift and signal sparsity, trigger alerts when accuracy degrades, and auto‑adjust or disable forecasts below confidence thresholds. Provide feature flags for gradual rollout, manual overrides for critical themes, and snapshot retention for reproducibility. Surface health dashboards to the team and emit metrics/logs to the observability stack.

Acceptance Criteria
Nightly Rolling-Origin Backtesting and Calibration
Given the nightly window 00:00–02:00 UTC, when the backtest job runs with a 90-day rolling-origin and 1-day step per theme cohort with ≥30 data points, then MAPE and RMSE are computed per cohort and persisted with run_id, cohort_id, and timestamp. Given a completed run, when evaluating prediction interval calibration over the last 60 rolling forecasts per cohort, then 80% PI coverage is within 78–82% and 95% PI coverage is within 93–97% (defaults; thresholds configurable) or the cohort is marked Uncalibrated. Given a successful run, when duration exceeds 30 minutes or failure rate >0%, then an alert glidepath_backtest_job_degraded is emitted with run_id and error summary. Given a rerun using the same snapshot and random seed, when executed, then backtest metrics reproduce within 0.1% absolute difference and the run hash matches.
Data Drift Monitoring and Alerting
Given daily feature distributions per cohort, when PSI ≥0.2 or KS-test p-value <0.01 versus a 14-day baseline, then the cohort is tagged Drifted and an alert glidepath_drift_detected is sent within 5 minutes with tenant, cohort_id, and top drifting features. Given a Drifted cohort, when drift resolves (PSI <0.2 and p-value ≥0.01) for 24 hours, then the Drifted tag is cleared and a recovery event is emitted. Given drift metrics, when alerts fire, then corresponding metrics and structured logs include run_id, version, thresholds, and observed values for auditability.
Guardrails: Confidence and Sparsity Auto-Adjust/Disable
Given per-cohort forecast_confidence computed from recent backtest performance, when confidence <0.60 or 7-day MAPE >0.30 (defaults; configurable), then widen prediction intervals by ≥25% or disable the forecast if confidence <0.50, setting status Disabled with reason LOW_CONFIDENCE. Given signal sparsity, when total new signals <20 over the last 7 days or there are ≥3 consecutive 0-signal days, then disable the forecast and label the cohort Sparse with reason SPARSE_SIGNAL and notify owners. Given any guardrail action, when applied, then it is logged with actor=system, reason code, previous state, new state, and is auto-reversed after 3 consecutive healthy days (confidence ≥0.65 and signals ≥20/7d).
Feature Flags and Gradual Rollout Controls
Given tenant- and theme-scoped feature flags, when glidepath_forecast is enabled for 10% of tenants and 100% of internal tenants, then only those scopes receive forecasts and others are withheld by default. Given the global kill switch glidepath_disable_all, when toggled, then all forecasts cease within 60 seconds and the UI shows a Disabled badge with reason FLAG_DISABLED. Given per-theme overrides, when a user toggles a theme flag, then the change propagates within 2 minutes and an audit entry records user_id, scope, old_value, new_value, and timestamp.
Manual Overrides for Critical Themes
Given a user with role Admin or PM, when they set a manual forecast with mean, 80/95% intervals, reason, and TTL, then the system serves the manual forecast for that theme until TTL expires or the override is removed. Given an active manual override, when forecasts are displayed or exported, then they are labeled Manual, include the reason, and are excluded from backtest accuracy metrics. Given override removal or TTL expiry, when the model resumes, then the first automatic forecast is recomputed within 5 minutes and a Resumed event is logged with run_id.
Snapshot Retention and Reproducibility
Given each forecast or backtest run, when outputs are produced, then a snapshot is saved containing model version, code hash, parameters, feature flags, training data time range and hashes, cohort/theme IDs, and random seed. Given a snapshot ≤90 days old, when a replay is requested with the same seed, then forecast means and interval bounds match within 1% absolute tolerance; discrepancies beyond tolerance are logged as replay_mismatch with diffs. Given retention enforcement, when snapshots exceed 90 days, then they are purged only after aggregated metrics are retained and purge events are audit-logged with counts and byte size.
Health Dashboard and Observability Emissions
Given the Glidepath Health dashboard, when loaded, then it shows per-cohort MAPE, RMSE, PI coverage (80/95), drift status, signal volume, confidence, last run time, and enablement state, and loads within 3 seconds at the 95th percentile. Given nightly runs, when metrics and logs are emitted, then metrics glidepath.backtest.mape, glidepath.backtest.rmse, glidepath.pi.coverage80, glidepath.pi.coverage95, glidepath.drift.psi, glidepath.signals.count, and glidepath.forecast.enabled are sent with labels tenant, cohort_id, theme_id, and run_id, and logs are structured JSON. Given alerting rules, when thresholds breach, then alerts are created in the observability stack with severity, runbook link, and on-call routing within 5 minutes of detection.

Threshold Coach

AI‑assisted recommendations set “Act,” “Watch,” and “Archive” thresholds per segment, tuned to your historical outcomes and alert fatigue. Cuts false positives and missed risks by calibrating sensitivity to what truly mattered for your product and customers.

Requirements

Segment-Aware Thresholding Engine
"As a product operations lead, I want the system to propose calibrated thresholds per segment so that our alerts reflect what truly matters to each customer group and reduce noise without missing risks."
Description

Core service that generates recommended Act/Watch/Archive thresholds per segment (e.g., customer tier, product area, channel, region) by learning from historical outcomes, severity, time-to-resolution, CSAT, churn indicators, and prior false positive/false negative rates. Accepts organization-level alert fatigue targets (e.g., max actionable alerts/user/week) and outputs calibrated thresholds with confidence intervals and expected alert volumes. Exposes REST/gRPC endpoints and writes accepted thresholds to a versioned configuration store. Supports cold start via global priors and incremental learning as new labeled outcomes arrive. Meets latency SLOs to update the live theme map and ranked queue in near real time.

Acceptance Criteria
Per-Segment Thresholds with Confidence and Volume Estimates
Given a request specifying segments by customer_tier, product_area, channel, and region with at least 100 historical labeled outcomes per segment in the past 90 days When the engine generates recommendations Then for each segment it returns numeric Act, Watch, and Archive thresholds And a 95% confidence interval for each threshold And expected weekly alert volumes for Act, Watch, and Archive And the response includes model_version and data_window used
Alert Fatigue Target Compliance
Given an org_alert_fatigue_target of N actionable alerts per user per week and a set of segments S When thresholds are generated Then the response includes expected Act volumes per segment and an aggregate expected Act volume across S And the aggregate expected Act volume per user per week across S is ≤ N And, when feasible based on historical distributions, the aggregate expected Act volume is within ±10% of N
Cold Start via Global Priors
Given no labeled outcomes exist for one or more requested segments When generating thresholds Then the service returns thresholds derived from global priors for those segments And marks those segments as cold_start in the response And includes 95% confidence intervals and expected weekly alert volumes for Act, Watch, and Archive
Incremental Learning and Near Real-Time Update SLOs
Given new labeled outcomes are ingested for a segment When the outcome event is processed Then updated thresholds for the affected segment are computed and written to the configuration store within 5 seconds p95 and 10 seconds p99 And the updated thresholds are queryable via REST and gRPC within 1 second after write And the live theme map and ranked queue reflect the new thresholds within 5 seconds p95 of the write
Versioned Configuration Store Write, Retrieval, and Rollback
Given thresholds for a segment are accepted When they are written to the configuration store Then a new immutable version is created with version_id, timestamp, actor, segment_keys, model_version, and alert_fatigue_target recorded And GET requests can retrieve the latest or any prior version by version_id And issuing a rollback to a prior version_id makes it the latest while preserving full history
REST and gRPC API Contract, Validation, and Idempotency
Given authenticated requests with valid payloads When calling POST /v1/thresholds:generate and rpc ThresholdsService.Generate Then responses return HTTP 200 OK and gRPC OK with equivalent schemas And invalid inputs (unknown segment keys, malformed targets) return HTTP 400 and gRPC INVALID_ARGUMENT with descriptive errors And duplicate acceptance requests with the same idempotency_key do not create multiple versions in the configuration store
Segment Coverage and Fallback Hierarchy
Given a request for thresholds across a hierarchy of segments (product_area > channel > region) When a child segment has fewer than 30 labeled outcomes in the last 90 days Then the engine uses the nearest ancestor’s priors to generate thresholds And the response includes a fallback_from indicator specifying the ancestor used And all segment keys in the response are unique and correspond to requested or fallback-resolved segments
Threshold Simulation & Backtesting
"As a product manager, I want to simulate threshold changes on past data so that I can choose settings that balance missed risks and noise before affecting production."
Description

Interactive simulation that replays historical data across candidate threshold settings to estimate impact before deployment. Presents projected alert volume, precision/recall proxies, expected changes in time-to-resolution, and correlations to CSAT/churn outcomes per segment. Supports what-if sweeps, pareto front visualization for noise vs. risk trade-offs, and exportable reports for stakeholders. Enables safe-guardrails (min precision, max fatigue) and schedules for gradual rollout.

Acceptance Criteria
Historical Replay Produces Segment Metrics
Given I select a historical window (start/end), one or more segments, and candidate Act/Watch/Archive thresholds And I choose the ground-truth labels used for proxy precision/recall When I run the simulation Then the system computes per-segment metrics: projected alert volume per week, proxy precision, proxy recall, delta median time-to-resolution vs baseline, and Pearson correlations to CSAT and churn with p-values And results are reproducible for the same inputs (identical outputs given the same dataset snapshot and parameters) And the run completes within 60 seconds for ≤12 months of data, ≤15 segments, and ≤200k items And all metrics reflect active filters (channels, languages, severities)
What‑If Threshold Sweep Across Grid
Given I define ranges and step sizes for Act/Watch/Archive thresholds per segment And I set a maximum combinations limit When I launch a sweep Then the system evaluates all combinations up to the limit and returns metrics for each And supports at least 5,000 combinations per run And allows pause/resume and cancellation without losing completed results And completes 5,000 combinations within 5 minutes or provides progress ETA and partial results
Pareto Front Trade‑off Visualization
Given I have sweep results with noise and risk metrics per combination When I open the trade-off view Then the system identifies the non-dominated set (Pareto front) based on minimizing alert volume/fatigue and missed-risk rate And renders the frontier and dominated points with selectable tooltips showing configuration and key metrics And allows pinning up to 3 configurations for side-by-side comparison And exports the frontier and selected points as CSV
Guardrails Enforcement for Precision and Fatigue
Given organization guardrails are configured (min proxy precision %, max alerts per owner per week) When a simulated configuration violates any guardrail Then the UI flags the violation with specific metric deltas And disables scheduling/deployment actions for that configuration And suggests the nearest passing configuration (by minimal change in thresholds) within 2 seconds And guardrail thresholds are editable by admins and are audited with timestamp and user
Gradual Rollout Scheduler with Preview
Given I select a passing configuration And I define rollout phases by date and segment coverage % When I create a schedule Then the system projects per-phase alert volume, proxy precision/recall, and fatigue metrics And blocks saving if any phase violates guardrails And stores the schedule as a versioned plan with author, timestamp, and changelog And supports rollback criteria (auto-revert if precision drops below X% or fatigue exceeds Y) configurable per schedule
Exportable Stakeholder Report
Given I have simulation results, selected configurations, and (optionally) a rollout schedule When I export a report Then the system generates a PDF and CSV bundle containing inputs, assumptions, per-segment metrics, Pareto summary, guardrail status, and rollout plan And includes dataset snapshot ID, parameter hash, and generation timestamp And renders charts legibly for print (A4/Letter) with color-blind-safe palette And completes export within 30 seconds for ≤5,000 combinations and ≤15 segments
Auditability and Reproducibility of Simulations
Given any simulation or sweep is executed When I view its details Then I can see dataset snapshot ID, code version, input parameters, user, and runtime stats And I can re-run the exact simulation from the record to produce identical outputs And all executions are retained for at least 90 days and searchable by date, user, segment, or snapshot ID And any changes to ground-truth labels or guardrails create a new version and invalidate prior cached results with clear notices
Explainable Recommendations
"As a CX lead, I want clear explanations of why a threshold is recommended so that I can trust it and communicate the decision to my team."
Description

Transparent rationale for each recommended threshold, including top contributing themes/signals, reason codes, supporting examples, sensitivity curves, and a confidence score. Surfaces data quality caveats and segment coverage. Provides inline guidance on how changes will influence the ranked queue and Jira/Linear sync volume. Exposes explanations via UI and API for auditability and stakeholder communication.

Acceptance Criteria
UI Explanation Completeness for Recommended Threshold
Given a user views a recommended threshold for a specific segment in Threshold Coach When they open the Explain panel Then it displays top 5 contributing themes/signals with individual contribution percentages and a total that sums to 100% ±1% And then it shows at least 1 reason_code from a controlled list And then it includes a minimum of 3 supporting examples with source type, timestamp, and a link to the original item with PII redacted And then it renders a sensitivity_curve with at least 20 plotted points covering the full threshold range And then it shows a confidence_score between 0.00 and 1.00 with two-decimal precision And then it shows model_version and generated_at timestamp in UTC And then the panel loads in ≤400 ms (p50) and ≤1200 ms (p95) when data is cached, and ≤1800 ms (p95) on cold start And then if any element is unavailable, a structured placeholder with a data_caveat reason_code is displayed and no broken UI elements appear
Data Quality Caveats and Segment Coverage Disclosure
Given a recommendation is generated for a segment When data completeness checks run Then coverage is displayed as percentages per channel and overall for the last 30 days and the current sampling window And then if overall coverage < 60% or sample_size < 200 items in the last 30 days, a visible warning badge with severity=warning is shown and confidence_score is reduced by at least 0.10 And then if any primary channel is entirely missing, a severity=critical caveat is displayed with reason_code=MISSING_CHANNEL and the affected channel listed And then caveats include actionable guidance text and a link to View data details that navigates to the data quality view And then all caveats are included in the API response under data_caveats[] with code, severity, and message fields
Inline Impact Guidance for Threshold Adjustments
Given a user adjusts the threshold slider or inputs a numeric threshold value for a segment When the value changes (including ±10% adjustments) Then the Impact Preview updates within ≤300 ms (p95) showing expected change in ranked_queue_count (delta and %) for the next 7 days And then it shows changes to the top 10 items (adds/removes) with counts And then it shows projected Jira/Linear sync volume for the next 7 days with an 80% prediction interval And then it shows estimated false_positive and false_negative deltas based on historical outcomes And then no changes are persisted until the user clicks Apply When Apply is clicked Then the new threshold is saved, the ranked queue recalculates within ≤2 s (p95), and sync rules update within ≤5 s (p95) When Revert is clicked before Apply Then the recommendation and impact preview return to the original state within ≤200 ms
Explanation API Contract and Performance
Given an authorized client with scope=explanations:read requests GET /v1/explanations?segment_id={id}&threshold={value} When the request is valid Then respond 200 application/json conforming to schema v1 with fields: explanation_id, segment_id, threshold_value, recommendation_type (Act|Watch|Archive), reasons[], top_contributors[] (name, contribution_pct), examples[] (source_type, id, timestamp, snippet_url), sensitivity_curve[] (threshold, metric), confidence_score, data_caveats[] (code, severity, message), coverage (per_channel%, overall%), model_version, generated_at (UTC), trace_id And then include ETag and Cache-Control: max-age=60 And then p95 latency ≤800 ms for segments with ≤50k items When include_audit=true Then audit_metadata (actor_id, generated_at, source_event_ids[]) is included When parameters are invalid Then respond 400 with error_code and message When unauthorized or forbidden Then respond 401 or 403 respectively
Shareable Explanation Report and Audit Log
Given a user clicks Share on an explanation in the UI When a share link is generated Then a signed URL is created with scope=view_explanation and default expiry of 14 days And then Export PDF and Export JSON actions produce files ≤5 MB within ≤5 s (p95) And then each export contains: explanation_id, segment_id, threshold_value, recommendation_type, reasons, top_contributors (up to 5), examples (up to 10 with redactions), sensitivity_curve snapshot, confidence_score, data_caveats, coverage, model_version, generated_at, impact_forecast summary And then each export includes a SHA-256 checksum and a watermark containing requester email and timestamp And then an audit log entry is recorded with actor_id, action (share_link|export_pdf|export_json), explanation_id, timestamp, and destination
Accessibility and Localization for Explanations
Given a user opens the Explain panel When navigating with keyboard only Then all interactive elements are reachable and operable with a visible focus state and logical order And then all text meets contrast ratio ≥4.5:1 and charts expose accessible data tables and ARIA labels for top_contributors and sensitivity_curve values And then screen readers announce the confidence_score and any data_caveat severity and message When locale is switched between en-US and es-ES Then UI strings, date/number formats, and caveat descriptions localize within ≤200 ms (p95); reason_codes remain stable but display localized descriptions And then the experience meets WCAG 2.2 AA in automated checks and passes manual keyboard and screen reader smoke tests
Human-in-the-Loop Overrides & Approval Workflow
"As an admin, I want to review and approve threshold changes so that we maintain control and accountability over what goes live."
Description

Review, adjust, and approve proposed thresholds via UI and API with side-by-side comparison of current vs. proposed settings and projected impact. Supports draft change sets, scheduled activation, instant rollback, and RBAC-based approvals. Maintains an immutable audit log of who changed what and when, with reason/comment capture for compliance. Integrates with org webhooks to notify stakeholders on approvals and activations.

Acceptance Criteria
Side-by-Side Threshold Proposal Review (UI)
- Given a proposed threshold set exists for a specific segment, When a reviewer opens the Threshold Coach review pane, Then the UI renders current vs. proposed values for Act/Watch/Archive side-by-side with numeric deltas and confidence scores within 2 seconds for up to 50 thresholds. - Given the review pane is open, When the reviewer edits a proposed value, Then inline validation prevents invalid ranges/units/non-numeric input and shows an error message within 200 ms. - Given edits are made, When the reviewer clicks "Save Draft", Then no production thresholds change and a draft change set is created. - Given the review pane is open, When the reviewer switches segments, Then the view updates without losing unsaved edits and prompts to save or discard changes.
Draft Change Set Creation and Save
- Given a user with Editor role saves a draft, When the save completes, Then a draft change set is created with immutable ID, name, description, target segments, proposed values, projected impact snapshot (alert volume delta and precision/recall deltas), author, and created_at timestamp. - Given a draft exists, When fetched via API GET /threshold-change-sets/{id}, Then the response returns status=draft, version number, and the exact data saved. - Given a draft is updated, When the user saves changes, Then a new version is created and prior versions remain read-only in history. - Given multi-tenant isolation, When a user from another tenant requests the draft, Then access is denied with 403 Forbidden.
RBAC-Based Multi-Step Approval
- Given role assignments, When a Viewer attempts to submit or approve, Then the action is blocked with 403 and UI controls are disabled. - Given a draft, When an Editor submits for approval, Then status changes to pending_approval and a comment is required. - Given org policy requires two approvers, When the author (Editor or Approver) attempts to be the sole approver, Then the system blocks self-approval and requires a second unique Approver. - Given an Approver approves with comment, When the second Approver also approves, Then status becomes approved and approval timestamps and approver IDs are recorded. - Given an Approver rejects with comment, Then status becomes rejected and the draft remains editable. - Given API usage, When POST /threshold-change-sets/{id}/approve is called without required role or comment, Then 403 or 400 is returned respectively.
Scheduled Activation and Deferred Changes
- Given an approved change set, When a user schedules activation for future UTC timestamp T, Then the system stores schedule=T (with tenant time zone context) and shows a countdown; no thresholds change before T. - Given time reaches T and the system is healthy, When activation executes, Then thresholds apply atomically within 60 seconds and status becomes active. - Given a scheduled activation exists, When a conflicting activation is created for the same segment/time, Then the system blocks it with a validation error. - Given downtime at T, When service recovers, Then activation executes within 5 minutes, sets activation_delayed=true, and logs delay duration. - Given a scheduled activation, When the user cancels before T, Then the schedule is removed and audit entries are created.
Instant Rollback to Prior Thresholds
- Given an active change set C1 and a previous active change set C0, When a user triggers rollback with reason, Then the system atomically reverts thresholds from C1 to C0 within 30 seconds, marks C1 as rolled_back, and marks C0 as active. - Given rollback completes, Then all dependent services reflect reverted thresholds within 60 seconds and new alerts use the reverted values. - Given rollback is triggered via API without reason/comment, Then the request fails with 400 and no changes are applied. - Given rollback occurs, Then a webhook event is sent and an audit entry is recorded.
Immutable Audit Log with Reason Capture
- Given any state change (create draft, update draft, submit, approve, reject, schedule, activate, cancel, rollback), Then an append-only audit entry is created containing actor_id, role, action, entity_id, before_values, after_values, reason/comment (required for submit/approve/reject/activate/rollback), timestamp (ISO-8601 UTC), and request_id. - Given the audit log API GET /audit?entity=threshold-change-sets, When queried with filters (action, actor_id, date range), Then results return within 1 second for up to 10k records and are not editable or deletable. - Given tamper-evidence, When retrieving a page, Then each entry includes a SHA-256 hash and previous_hash enabling chain verification; any mismatch flags integrity_error=true. - Given export is requested, When a user clicks "Export CSV", Then the system generates a CSV within 30 seconds for up to 100k entries and provides a signed download URL.
Webhook Notifications on Approvals and Activations
- Given org webhooks are configured with a shared secret, When events occur (submitted_for_approval, approved, rejected, scheduled, activated, rolled_back), Then the system sends POST requests within 5 seconds containing JSON payload with event_type, tenant_id, change_set_id, status, actor, timestamp, and threshold_deltas summary. - Given delivery to the subscriber, When the receiver returns non-2xx, Then the system retries with exponential backoff for up to 24 hours (max 15 attempts) and marks final status=failed if not delivered. - Given security requirements, When sending, Then requests are signed with HMAC-SHA256 using the secret in header X-Echolens-Signature and include an idempotency_key; duplicate deliveries for the same key must be ignored by receivers. - Given UI delivery logs, When viewing a change set, Then a webhook delivery history shows attempt timestamps, response codes, and allows redelivery of failed attempts.
Real-time Performance Monitoring & Drift Alerts
"As a PM, I want to be alerted when threshold performance degrades so that we can re-calibrate before customers are impacted."
Description

Continuous tracking of threshold effectiveness and alert fatigue across segments, including precision/recall proxies, alert volume, escalation rates, and time-to-resolution. Detects data or concept drift in theme distributions and outcome labels, triggering re-tune suggestions or auto-retraining under guardrails. Sends Slack/email alerts when KPIs breach configurable thresholds and opens recommended change sets for review.

Acceptance Criteria
Segment KPI Telemetry Visible in Real Time
Given EchoLens is ingesting new feedback and alerts across sources per segment When the monitoring job computes per-segment KPIs on a 7-day rolling window with a 60-second cadence Then the dashboard displays for each segment: alert_volume, escalation_rate, median_time_to_resolution, precision_proxy, recall_proxy, and last_updated_at And values reflect new data within 60 seconds of event ingestion And filtering by segment, source, and time range updates metrics within 2 seconds client-side and 5 seconds server round-trip And CSV export for the same filter returns numbers within ±0.1% of on-screen values And if sample_size < 30 in the window, precision_proxy and recall_proxy are labeled "Insufficient data" and excluded from alerting
Precision/Recall Proxy Computation Per Segment
Given each alert is labeled TP or FP upon resolution and missed incidents are labeled FN within 14 days When KPIs are computed for a segment over a 7-day rolling window with sample_size ≥ 30 Then precision_proxy = TP/(TP+FP), rounded to 2 decimals And recall_proxy = TP/(TP+FN), rounded to 2 decimals And counts (TP, FP, FN, sample_size) are available via API GET /kpi and match the dashboard And late labels update the prior 14 days and trigger recomputation within 5 minutes
Configurable KPI Breach Notifications via Slack/Email
Given an admin defines breach rules per segment (e.g., precision_proxy < 0.75 for 3 consecutive windows or alert_volume > 150/day) And delivery channels Slack #product-ops and email ops@company.com are configured When a rule condition is met Then a single Slack message is sent within 90 seconds containing segment, KPI, current value, threshold, window, link_to_details, and recommended_action And an email with the same payload is delivered within 2 minutes And duplicate notifications are suppressed for 30 minutes unless the KPI worsens by ≥5% relative And acknowledgements in Slack stop repeats for 24 hours and are recorded in the audit log
Theme Distribution and Outcome Label Drift Detection
Given baseline = previous 14 days and current = last 24 hours per segment with sample_size ≥ 200 When PSI for theme distribution > 0.25 or outcome positive-rate change exceeds 3 standard deviations vs. baseline Then a drift alert is created containing drift_type, affected_segments, magnitude, top_5_shifting_themes, p_value (if applicable), and link_to_diff And multiple testing across segments is controlled at FDR 5% using Benjamini–Hochberg And drift alerts include a suggested retune action with estimated KPI impact And if sample_size < 200, no drift decision is made and status is "Insufficient data"
Guardrailed Auto-Retuning and Auto-Retraining
Given drift is active or KPI degradation persists (precision_proxy drop ≥10% or recall_proxy drop ≥10%) for ≥24 hours with sample_size ≥ 300 When proposed threshold adjustments are within ±20% of current values and retraining uses the same model family with hyperparameter deltas ≤10% Then a shadow evaluation on the last 14 days demonstrates F1_proxy lift ≥5% with p < 0.05 And a canary rollout to 10% of affected segments for the next 200 alerts or 2 hours (whichever is later) shows non-degraded precision_proxy and recall_proxy versus control And upon canary success, rollout to 100% completes automatically and changes are recorded with version, approver, and rollback token And on canary failure, auto-rollback occurs within 2 minutes and a review task is created
Change Set Proposal, Review, and Tracker Sync
Given a recommended change set is generated for one or more segments When a user opens the review drawer Then the UI shows the proposed diffs, rationale, before/after KPIs, affected segments, and expected alert volume change And clicking Approve applies the change set within 60 seconds, posts a summary to Slack, and creates a Jira/Linear ticket using the "Threshold Coach Update" template populated with segment, diffs, KPIs, and roll-forward plan And clicking Reject requires a reason, records it in the audit log, and suppresses re-suggesting the same change set for 7 days And API responses for apply/reject are idempotent and return 2xx with operation_id
Theme Map and Workflow Integration
"As a triage owner, I want Act/Watch/Archive states to drive our live theme map and downstream Jira/Linear workflows so that high-impact fixes are pushed to the top with minimal manual effort."
Description

Applies Act/Watch/Archive states to the live theme map and ranked queue in real time, reordering items based on calibrated thresholds. On activation, auto-creates or updates Jira/Linear issues for Act items with segment context, links back to EchoLens, and sets watchers/labels. Ensures idempotent sync, includes threshold metadata in payloads, and provides API endpoints for external systems to read current states. Includes feature flags and gradual rollout per segment.

Acceptance Criteria
Real-time Threshold Application and Reordering
Given Threshold Coach recalculates thresholds for a segment and produces new Act/Watch/Archive states When the change is saved Then the live theme map and ranked queue reflect the new states within 2 seconds for 95% of updates and within 5 seconds for 100% And items are reordered to prioritize Act over Watch over Archive, then by priority score (desc), then by lastSeenAt (desc) as a deterministic tie-breaker And a transient toast displays the count of items moved and the timestamp of the update And state changes are recorded in an audit log including userId (or system), segmentId, thresholdVersion, and changedAt
Auto-Creation and Update of Jira/Linear Issues for Act Items
Given Jira or Linear is connected and activation is ON for a segment When a theme enters Act or is created with state Act Then exactly one issue is created in the configured tool containing title, description summary, segment context, labels, watchers, and a deep link back to EchoLens And a stable externalId/custom field is set so retried or repeated syncs update the same issue instead of creating duplicates And Watch and Archive items do not create or update issues And failures are retried up to 3 times with exponential backoff and surfaced in an integration health panel with error codes
Idempotent Sync on Repeated Runs and Retries
Given the same Act item is synchronized multiple times within 24 hours for the same segment When the integration processes these events Then no duplicate issues, comments, or labels are created in Jira/Linear And only drifted managed fields are updated, preserving user-edited fields unless flagged as managed And each sync run records a correlationId and a deduplication outcome (created|updated|skipped) in logs
Threshold Metadata in Outbound Payloads
Given an Act item is synced to a work tracker or an API client requests current theme states When the payload is generated Then it includes thresholdLevel (Act|Watch|Archive), confidenceScore (number), segmentId, calibrationVersion, ruleSource, and changedAt (ISO 8601 UTC) And enums are strings from the documented set, numeric fields are numbers, and timestamps are RFC3339/ISO 8601 And absent values are omitted (not null) unless the schema explicitly requires null And a payload version field is present to ensure backward-compatible evolution
External API to Read Current Theme States
Given a client with valid API credentials When it calls GET /v1/themes?segmentId={id}&state={state}&updatedSince={ts}&limit={n} using cursor-based pagination Then the response is 200 OK with items whose states match the UI within 1 second of change (eventual consistency SLA) And responses include nextCursor, ETag, and totalCount (approximate) and support If-None-Match for caching And rate limiting of 120 req/min per token is enforced with 429 and Retry-After headers And p95 latency is <= 300 ms for pages up to 100 items in the primary region And 401/403 are returned for invalid/insufficient auth and 400 for invalid params with machine-readable error codes
Feature Flags and Gradual Rollout per Segment
Given an admin enables the Theme Map and Workflow Integration flag for a segment at X% rollout When users in that segment access the product Then only users allocated by the rollout receive the new behavior and control users retain the old behavior And allocation is stable per user for at least 30 days via consistent hashing on userId (or stable key) And increasing rollout percentage expands the treated cohort without reassigning existing users And turning the flag OFF reverts behavior within 60 seconds for the segment and cancels in-flight sync jobs And all changes are captured in audit logs with actor, old/new values, segmentId, and timestamp
Live Theme Map Accessibility and Visual Indicators
Given themes have Act/Watch/Archive states When the theme map renders Then each state is indicated by a distinct color, icon, and ARIA label with WCAG contrast ratio >= 4.5:1 And a legend explains states and tooltips show state, confidenceScore, segment, and lastChangedAt And full keyboard navigation supports moving between themes and opening Jira/Linear links without a mouse And visual updates do not cause layout shifts exceeding CLS 0.1

Outlier Shield

Automatically detects and downweights anomalies like duplicate tickets, bot floods, or viral threads that can distort velocity. Keeps Theme Health Scores trustworthy and stable, so Rapid Shippers focus on real customer impact—not noisy spikes.

Requirements

Real-time Multi-channel Anomaly Detection
"As a product manager who ships weekly, I want EchoLens to automatically detect and label anomalous feedback spikes across all channels in real time so that Theme Health Scores aren’t distorted by noise."
Description

Implements a streaming detection layer that identifies and tags anomalous feedback across all ingested channels (reviews, emails, tickets, chats) with subtype and confidence (e.g., duplicate burst, bot flood, viral spike). Combines statistical spike detection (per-theme, per-channel, per-tenant baselines), density-based outlier detection on text embeddings, and metadata heuristics (e.g., referrer URL, IP reputation, sender auth) to compute an anomaly_weight per item. Integrates inline with the ingestion and clustering pipelines to apply downweighting before Theme Health Score computation and ranked queue generation, with end-to-end latency under 2 seconds at P95. Outputs structured anomaly metadata to the live theme map so users see stable, trustworthy trends while maintaining full item traceability.

Acceptance Criteria
P95 Latency ≤2s from Ingest to Queue and Theme Map
Given a live stream of 10,000 mixed feedback items across reviews, emails, tickets, and chats for a single tenant over 10 minutes with ≥20% labeled anomalies When items are ingested via the streaming pipeline Then the P95 latency from ingestion timestamp to availability in the ranked queue is ≤ 2,000 ms And the P95 latency from ingestion timestamp to visualization on the live theme map with anomaly metadata is ≤ 2,000 ms And downweighting is applied before the item first appears in the ranked queue and before the Theme Health Score is recalculated
Multi-channel Anomaly Tagging with Subtype and Confidence
Given seeded scenarios of duplicate bursts, bot floods, and viral spikes across reviews, emails, tickets, and chats totaling 2,000 anomalous and 8,000 normal items When the detection layer processes the stream at default thresholds Then each anomalous item is tagged with subtype in {duplicate_burst, bot_flood, viral_spike} and confidence in [0,1] And per channel and subtype, precision ≥ 0.90 and recall ≥ 0.80 for true anomalies at confidence ≥ 0.5 And the false positive rate for normal items is ≤ 2% per channel at confidence ≥ 0.5
Hybrid Detector Quality (Embeddings + Heuristics)
Given a labeled corpus of 5,000 feedback items with 10% semantic outliers per theme and available IP reputation, referrer URL, and sender auth metadata When density-based outlier detection on text embeddings is combined with metadata heuristics at default settings Then outlier detection achieves AUROC ≥ 0.92 and F1 ≥ 0.85 on a held-out test set And ablation tests show embeddings-only F1 ≥ 0.78 and heuristics-only F1 ≥ 0.70 And per-item scoring provides the top 3 reason_codes contributing to the anomaly decision
Per-Theme/Channel/Tenant Statistical Spike Detection
Given 30 days of historical per-theme, per-channel, per-tenant counts with no injected spikes When the statistical spike detector runs at default sensitivity Then the false positive rate for spike alerts is ≤ 1% of evaluated intervals And given an injected 3× sustained increase over baseline for 5 consecutive minutes Then the detector emits a viral_spike subtype within 60 seconds of spike onset And affected items during the spike window are tagged with confidence ≥ 0.80
Downweighting Stabilizes Theme Health Scores and Rankings
Given baseline Theme Health Scores and rankings computed from the prior 7 days When a 15-minute bot flood of 500 items targets a single theme without a genuine increase in unique customers Then the theme's Health Score deviates by ≤ 10% from baseline during and 30 minutes after the event And the theme's rank shifts by ≤ 1 position due solely to flagged anomalous volume And genuine items within the window retain effective weight within ±5% of 1.0
Inline Downweighting Before Clustering and Score Computation
Given a high-confidence anomalous item (confidence ≥ 0.90) When the item enters the ingestion and clustering pipelines Then an anomaly_weight is computed before clustering and Theme Health Score calculation And the item's effective contribution to clustering and scoring is reduced to ≤ 20% of a normal item's contribution And the ranked queue reflects reduced influence in the same processing cycle as ingestion
Structured Anomaly Metadata and Full Traceability
Given any item tagged as anomalous When the item is retrieved via API or viewed on the live theme map Then the following fields are present and non-null: subtype, confidence, anomaly_weight, reason_codes[], detection_timestamp, source_channel, tenant_id, detector_versions And links resolve to the original raw item (ticket_id, email_message_id, chat_id, or review_id) for audit And CSV/JSON export includes the same fields to enable reproducibility
Cross-channel Duplicate Canonicalization
"As a CX lead, I want duplicate reports to be collapsed into a canonical item with traceable links so that I see true impact without losing context needed to follow up."
Description

Detects and collapses semantically equivalent or near-duplicate feedback items across sources into a single canonical record with child links, preserving provenance and agent workflows. Uses semantic similarity on embeddings, fuzzy matching on titles/bodies, and metadata (account, device, session) to identify duplicates while preventing over-merge. Only the canonical instance contributes full weight; duplicates contribute diminishing marginal weight based on actor diversity. Integrates with clustering so themes reflect unique signal volume, and updates the live theme map and ranked queue without losing the ability to drill into individual reports.

Acceptance Criteria
Cross-Channel Duplicate Merge on Ingestion
Given a labeled validation set of 1,000 feedback items across email, chat, tickets, and reviews containing 250 known duplicate groups (size 2–5) When canonicalization runs on ingestion Then it achieves ≥0.92 pairwise precision and ≥0.88 pairwise recall on duplicate identification And creates exactly one canonical record per duplicate group with child links to all group members And end-to-end canonicalization latency is ≤3 seconds at P95 per new item
Provenance and Agent Workflow Preservation
Given a duplicate group collapsed into a canonical record When viewing the canonical in EchoLens Then each child link exposes source channel, account ID, author identifier, device family, session ID, timestamps, and original agent/queue assignment And agents retain ability to reply/close child items in their native tools with status mirrored back within 30 seconds And no original content, attachments, or tags are lost (checksum equality on stored bodies passes)
Actor-Diversity-Based Weighting to Theme Health
Rule: canonical item contributes weight 1.0; each additional duplicate from a distinct actor/account contributes 0.15; each additional duplicate from the same actor contributes 0.03; total weight capped at 1.8 Given a canonical with N children and K distinct actors When computing theme health score contribution Then the computed weight equals min(1.0 + 0.15*K + 0.03*(N-K), 1.8) And two cases validate: (a) N=5, K=3 -> weight=1.51; (b) N=5, K=1 -> weight=1.27
Over-Merge Guardrails Using Metadata and Thresholds
Rule: Auto-merge threshold is cosine similarity ≥0.92 on embeddings AND at least one matching key field among {account ID, device family, app version, session fingerprint} Rule: For 0.85 ≤ similarity < 0.92, require ≥2 matching key fields; otherwise do not auto-merge Rule: If account ID and device family both conflict, do not auto-merge regardless of similarity; queue for review Given a labeled holdout set When applying auto-merge rules Then false-positive merge rate is ≤1% and manual-review coverage captures ≥95% of ambiguous pairs (0.80 ≤ similarity < 0.85 or rules conflict)
Real-Time Updates to Theme Map and Ranked Queue
Given a canonicalization event (merge, unmerge, or new duplicate added) When the system updates aggregates Then the live theme map node counts and edges refresh within 5 seconds at P95 And the ranked queue reorders within 5 seconds at P95 based on updated weights And duplicates do not inflate theme counts beyond the configured weighting function
Drill-Down from Theme/Queue to Canonical and Children
Given a user opens a theme cluster or queue item representing a canonical record When they click through to details Then the canonical detail view loads within 2 seconds at P95 and lists all children with channel, account, and timestamp And the user can open any child’s full raw content in ≤2 clicks And filters by channel and account are available and correctly constrain the child list
Single-Issue Sync to Jira/Linear with Idempotency
Given a canonical record with children When the user triggers "Sync to Jira/Linear" Then exactly one external issue is created/updated and all child items are linked/mentioned within that issue And repeated syncs are idempotent (no duplicate issues; issue key remains unchanged) And arrival of a new child adds a link/comment to the existing external issue within 15 seconds
Source Trust Scoring & Flood Throttling
"As a Rapid Shipper, I want bot floods and coordinated spam to be automatically downweighted or quarantined so that my prioritization reflects real customers, not automation."
Description

Assigns dynamic trust scores to sources (domains, mailboxes, forms, chat endpoints) based on historical signal quality, authentication, and reputation, and applies rate limits and quarantine rules when low-trust bursts occur. Detects bot-like patterns (high velocity, identical payloads, abnormal entropy) and automatically downweights or quarantines suspect items pending review. Trust scores feed the anomaly_weight and Theme Health Score, with safelist/blocklist controls and auto-recovery to avoid permanently suppressing legitimate senders. Integrates with ingestion gateways and emits explanatory tags to downstream services.

Acceptance Criteria
Dynamic Trust Score Calculation per Source
Given a source without prior history, When its first item is ingested, Then the system initializes trust_score to 0.50 within [0.00,1.00] and persists created_at/updated_at and rationale tag "no_history". Given any new item for a source, When processing completes, Then trust_score is recalculated within 60 seconds using at least the last 90 days of signals and persisted atomically with versioning. Given trust_score is recalculated, Then an audit record is written including source_id, old_score, new_score, timestamp, and primary contributing features, and the record is queryable by source_id within 60 seconds. Given trust_score exists for an item, Then it is available to downstream scoring jobs before the item is considered for Theme Health Score (trust_score timestamp <= item scoring timestamp).
Authentication and Reputation Signals Influence Trust
Given a source with DKIM/SPF pass rate >= 95% across the last 200 email items, When trust_score is recomputed, Then trust_score increases by >= 0.10 versus a computation with DKIM/SPF pass rate <= 50%, all else equal, and the adjustment is tagged "strong_auth". Given a source's domain reputation is "poor" from the configured provider in the last 7 days, When trust_score is recomputed, Then trust_score decreases by >= 0.20 and is tagged "low_reputation". Given a chat integration authenticated via OAuth with verified app identity, When messages are ingested, Then trust_score is adjusted upward by >= 0.05 and tagged "strong_auth". Given authentication fails for >= 3 consecutive items within 1 hour, When trust_score is recomputed, Then trust_score drops below 0.40 and items during the failure window are tagged "auth_fail".
Low-Trust Burst Rate Limiting and Quarantine
Given a source with trust_score < 0.40, When its 5-minute item count exceeds max(50, 5x its 7-day rolling 5-minute baseline), Then the system throttles to 10 items/minute and places overflow items into quarantine. Given a source with trust_score <= 0.20, When any new item arrives, Then the item is quarantined immediately and not emitted to downstream scoring. Given the ingestion path is an HTTP webhook, When throttling is active, Then responses to the sender are HTTP 429 with a Retry-After header equal to the remaining throttle window in seconds. Given items are throttled or quarantined, When Theme Health Score is computed, Then those items are excluded from anomaly_weight and Theme Health Score until released, and the exclusion is reflected in metrics as out_of_flow_count.
Bot-like Pattern Detection and Downweighting
Given >= 30 items from the same source within 5 minutes where pairwise cosine similarity of message vectors >= 0.95 or payload hash matches, When processed, Then the items are tagged "duplicate_burst" and "bot_like" and assigned anomaly_weight <= 0.20. Given a burst whose character-level entropy z-score is <= -3.0 relative to the source's 30-day baseline, When processed, Then the items are tagged "low_entropy" and downweighted to anomaly_weight <= 0.20 or quarantined if trust_score < 0.40. Given items flagged "bot_like", When they are included in any theme cluster, Then their contribution is reduced proportionally to trust_score and anomaly_weight, and the reduction appears in the Theme Health Score within 2 minutes.
Safelist and Blocklist Overrides
Given an admin safelists a source id, domain, or endpoint, When the next item arrives, Then trust_score for that source has a floor of 0.80, rate limiting and quarantine are bypassed, and items still carry diagnostic tags without downweighting. Given an admin blocklists a source id, domain, or endpoint, When the next item arrives, Then the item is rejected or quarantined with cause "blocklisted", no downstream events are emitted, and trust_score is set to 0.00. Given a safelist or blocklist change is made, When saved, Then it propagates to enforcement within 60 seconds and an immutable audit entry is created with actor, scope, reason, and before/after scope counts.
Auto-Recovery of Trust After Incident
Given a source previously fell below 0.40 trust_score, When 200 consecutive items over 7 days have no "bot_like" or "duplicate_burst" tags and DKIM/SPF pass rate >= 95%, Then trust_score returns to within 10% of its pre-incident 30-day average or to >= 0.60, whichever is higher. Given no throttling or quarantine triggers for 24 continuous hours, When the window elapses, Then all active rate limits for the source are lifted automatically and the source exits quarantine mode. Given a safelist is applied to a source with items in quarantine, When applied, Then all quarantined items from the last 24 hours are re-evaluated and released within 2 minutes if they have no blocklist violations.
Explanatory Tags and Downstream Impact
Given any item is downweighted, throttled, or quarantined, When the event is emitted to downstream services, Then it includes tags with cause codes, contributing features and numeric values, computed trust_score, anomaly_weight, and enforcement action. Given downstream clustering and scoring services receive tagged items, When Theme Health Score is recomputed, Then quarantined items are excluded, and non-quarantined items are weighted by weight = base_weight * trust_score (clamped [0.0,1.0]). Given a reviewer releases a quarantined item, When released, Then the item is re-emitted with tag "quarantine_released" and included in the next scoring cycle within 2 minutes.
Viral Thread Spike Dampening
"As a product manager, I want temporary viral surges to be smoothed so that themes rise due to persistent demand, not a one-off thread."
Description

Identifies cascades originating from a single catalyst (e.g., social thread, forum post, newsletter) by grouping items via shared URLs/referrers and temporal proximity, then caps marginal weight per thread and applies temporal decay smoothing. Requires diversity across users/accounts and sustained multi-day signal before relaxing dampening to avoid suppressing legitimate emerging issues. Provides tunable thresholds per tenant and A/B calibration to balance sensitivity vs. recall. Integrates with Theme Health Score computation and the live theme map to prevent whiplash from transient virality while preserving true trend shifts.

Acceptance Criteria
Viral Thread Detection via Shared Referrer within Window
Given tenant T with spike_detector config: min_thread_size=20, thread_window_hours=3, referrer_match=exact_normalized_url And a dataset of 50 feedback items where 30 items share referrer_url=https://forum.example.com/t/123 and created_at within 3 hours, and 20 items have different referrers or fall outside the 3-hour window When Outlier Shield runs viral thread detection Then one viral_thread cluster is created with a unique thread_id containing exactly those 30 items And items outside the window or with different referrers are excluded from the cluster And cluster.start_time equals the minimum created_at and cluster.end_time equals the maximum created_at of the clustered items And detection latency from the last qualifying item to cluster availability is less than or equal to 60 seconds
Per-Thread Marginal Weight Cap Applied to Theme
Given tenant T with cap_marginal_weight_per_thread=25 And a theme Y receiving 200 items all linked to viral_thread_id=vt_abc When Theme Health Score is computed Then the total marginal contribution from vt_abc to theme Y equals 25 And adding 100 more items to vt_abc does not increase the contribution beyond 25 And if a second viral thread vt_def contributes 80 items to the same theme, its contribution is separately capped at 25 and the combined capped contribution equals 50 And an audit record is emitted showing thread_id, raw_count, applied_cap=25, and timestamp
Exponential Temporal Decay Smoothing for Spike Contributions
Given tenant T with decay_model=exponential, half_life_hours=12, smoothing_interval_minutes=15, and cap_marginal_weight_per_thread=40 And a viral_thread vt_abc with 100 qualifying items all arriving within a single 15-minute interval at time t0 When smoothing is applied Then the per-interval contribution at t0 is v0, the contribution at t0+12h is within 0.5±5% of v0, and at t0+24h is within 0.25±5% of v0 And the sum of all future per-interval contributions equals 40 within ±1% And the next interval’s smoothed value is computed and visible in the UI/API within 60 seconds of the interval close
Dampening Relaxation via Diversity and Sustained Multi-Day Signal
Given tenant T with diversity thresholds: min_distinct_accounts=10, min_distinct_users=20, sustain_days=3, min_daily_non_thread_items=5 And a theme Y initially under dampening due to viral_thread vt_abc And for 3 consecutive days after vt_abc peak there are at least 5 non-thread items per day mapping to theme Y from ≥10 distinct accounts and ≥20 distinct users When the daily diversity evaluation job runs Then dampening_state for theme Y transitions to "off" within 24 hours of meeting thresholds And subsequent Theme Health Score computations include full weight from non-thread items and vt_abc is no longer capped And if on any following day the non-thread items fall below any threshold, dampening_state reverts to "strict" within 24 hours
Per-Tenant Tunable Thresholds and A/B Calibration Workflow
Given tenant T splits traffic into cohorts A and B by stable hashing of account_id (50/50) And cohort A config: min_thread_size=20, thread_window_hours=3, cap=25; cohort B config: min_thread_size=10, thread_window_hours=1.5, cap=40 And a labeled set of incident spikes is available for offline evaluation When the system runs for 14 days Then per-cohort metrics are computed: spike_precision, spike_recall (vs labeled incidents), theme_score_volatility (stddev of daily score), and time_to_dampening And an admin can promote the better-performing cohort config to 100% with a single action via UI/API And promotion is blocked if labeled-incident recall would decrease by more than 10% relative to the current config And all parameter changes are versioned and auditable with timestamp, actor, old_value, new_value
UI and API Integration with Theme Health Score and Live Map
Given a theme Y affected by a viral thread vt_abc and Outlier Shield is active When the Live Theme Map and Theme Health Score endpoints are queried every 15 minutes Then displayed Theme Health Scores use dampened and smoothed values and do not change by more than 1 severity bin within any 15-minute interval in a controlled test where vt_abc is the only varying input And the theme detail API includes fields: viral_thread_ids, dampening_applied (boolean), applied_cap_value, decay_model, half_life_hours, diversity_state And toggling "Show raw (unshielded) signal" displays unsmoothed/uncapped values and the numeric delta equals shielded_value - raw_value within ±1% And all values are consistent between UI and API for the same timestamped snapshot
Explainable Weighting & Admin Overrides
"As a PM, I want to understand why items were downweighted and override when necessary so that I can trust the scores and correct edge cases quickly."
Description

Surfaces human-readable explanations for each weight adjustment at item and theme levels, including which rules/models triggered, confidence scores, and contribution breakdown to Theme Health Score. Provides UI affordances (tooltips, drill-down, rule badges) and an audit trail with model/rule versions and timestamps. Enables role-based overrides to adjust weights or release quarantined items with mandatory notes; changes are propagated to the ranked queue, live theme map, and sync payloads. Explanations and overrides are exposed through API and webhooks for downstream transparency.

Acceptance Criteria
Item-Level Explanation: Tooltip, Drill-Down, and Rule Badges
Given an anomaly-affected item has been weighted by Outlier Shield When a user hovers the weight badge Then a tooltip shows pre_weight, final_weight, each triggered rule/model name and version, and each confidence score with its delta contribution Given the user clicks "View details" on the item When the details modal opens Then the sum of all listed adjustment deltas equals (final_weight − pre_weight) within 0.001 and all values display with two decimal places Given rule/model badges are rendered on the item card When the details modal is open Then the set of badges matches the rules/models listed in the modal 1:1 Given the item belongs to a theme When viewing the details modal Then the item's percentage contribution to the theme Health Score is displayed and matches the theme-level breakdown
Theme-Level Health Score Contribution Breakdown
Given a theme with at least 5 items, some downweighted or quarantined When a user opens Theme Insights Then the contributions list shows each item's post-weight contribution and the sum equals the displayed Theme Health Score within 0.01 Given theme-level rules/models influence aggregation When viewing the Theme Explanation panel Then the panel lists each contributing rule/model, its version, aggregate confidence, and net delta to the theme score Given a theme was recomputed When the Theme Insights panel loads Then a "Last recompute" timestamp in ISO 8601 UTC is shown and matches the latest audit event for that theme
Role-Based Overrides with Mandatory Notes and Quarantine Release
Given a user with permission override:weights When they set an item weight to a value between 0.00 and 1.00 and enter a note Then the note is mandatory (minimum 10 characters), the change is saved, and an override badge appears on the item Given a quarantined item When an authorized user selects "Release from quarantine" and submits a note Then the item is reintroduced into calculations and marked as "Released by override" with the note displayed in details Given a user without permission override:weights When they attempt to change a weight or release an item Then the system responds with 403 Forbidden and no audit record or recalculation occurs
Propagation of Overrides to Queue, Theme Map, and Sync
Given any automatic or manual weight change is committed When the system processes recalculation Then the ranked queue updates order within 5 seconds and the live theme map reflects new values within 5 seconds Given Jira/Linear integrations are connected When the next sync cycle runs or a manual push is triggered Then sync payloads include updated weights, explanations, and override notes and a weight.changed webhook is delivered within 60 seconds Given an override was applied When viewing the ranked queue and theme map Then both surfaces display labels or badges indicating that weights are influenced by overrides for affected items/themes
Audit Trail with Model/Rule Versions and Timestamps
Given an automatic model-triggered adjustment occurs When the audit subsystem records the event Then the record contains event_id, item_id/theme_id, actor=system, model_name, model_version, rule_name, rule_version, before_weight, after_weight, confidence, source=auto, and timestamp in ISO 8601 UTC Given a manual override is saved When the audit record is created Then it includes actor_user_id, note, before_weight, after_weight, source=manual_override, and links to any affected sync events Given any attempt is made to modify or delete an audit record When the operation is executed Then the system rejects the change and logs a separate immutable audit event of the denied attempt Given an audit query is made via UI or API When filtering by item_id or date range Then results return in reverse chronological order and exactly match the corresponding item/theme details
API and Webhook Exposure of Explanations and Overrides
Given an authenticated client calls GET /items/{id}/explanation When the request succeeds Then the response includes pre_weight, final_weight, adjustments[], rules[], models[], confidence_scores[], contribution_to_theme, model_version, rule_versions, timestamp, and override_note if present Given an authenticated client calls GET /themes/{id}/explanation When the request succeeds Then the response includes an array of item contributions whose sum equals the Theme Health Score within 0.01 and lists any theme-level rules/models with versions Given a webhook subscription exists When an item weight or explanation changes Then a weight.changed event is sent with HMAC-SHA256 signature, includes audit_event_id, previous_values, new_values, reason, and delivers up to 3 retries with exponential backoff on failure
Historical Reprocessing & Score Versioning
"As a data-conscious PM, I want historical scores recalculated after detection improvements so that my trendlines stay consistent and trustworthy."
Description

Adds a backfill pipeline to reprocess historical feedback when detection rules/models change, recomputing anomaly weights and Theme Health Scores over a configurable window. Stores versioned scores and change logs, enables side-by-side comparison of old vs. new scores, and supports rollback. Provides job scheduling with idempotency, progress tracking, and SLAs, and emits notifications/webhooks when score versions change to keep dashboards and integrations consistent. Ensures longitudinal trendlines remain comparable and auditable after improvements to Outlier Shield.

Acceptance Criteria
Reprocess Historical Feedback on Model/Rule Update
Given an Outlier Shield model/rule version v is published and a backfill window of N days is configured When a user triggers reprocessing via UI or API with parameters {version: v, window_days: N} Then the system enqueues backfill jobs for all tenants/themes with feedback timestamps within the window And recomputes anomaly weights and Theme Health Scores using version v And writes new score records tagged with score_version_id and model_version v without mutating prior versions And the active score version remains unchanged until a publish action occurs
Idempotent Backfill Scheduling with Progress & SLA
Given a backfill request with identical parameters is submitted multiple times When the system processes the requests Then only one job is executed and subsequent duplicates are deduplicated or coalesced And the job exposes status states {queued, running, completed, failed}, percent_complete, items_processed, and ETA via UI and API And the job is resumable on retry without reprocessing already-completed items And for datasets up to 100,000 feedback items, the 95th percentile completion time is <= 30 minutes, and for up to 1,000,000 items, <= 4 hours
Versioned Score Storage and Change Log Auditability
Given reprocessing completes Then each theme score record is stored with fields {theme_id, time_bucket, score, anomaly_weight, score_version_id, model_version, processed_at} And a change log entry is recorded per theme with fields {from_version, to_version, delta, percent_change, reason, triggered_by, job_id} And the API supports querying scores by score_version_id and returns the active version by default And the system retains at least the last 5 score versions per tenant
Side-by-Side Score Comparison (UI and API)
Given at least two score versions exist for a tenant within the same time window When a user selects versions A and B Then the UI and API return per-theme absolute and percent differences, confidence score shifts, and a ranked list by impact And results are filterable by time range, theme, and change magnitude thresholds And export to CSV/JSON returns the compared dataset with version identifiers
Rollback to Prior Score Version
Given version B is active and version A exists When a user triggers a rollback to version A for a selected scope {tenant: T, time_window: W} Then version A becomes the active version for that scope And dashboards, live theme maps, and integrations reflect version A within 5 minutes And caches are invalidated and a change log entry is recorded with reason "rollback"
Notifications and Webhooks on Score Version Changes
Given a score version is published or rolled back When the event occurs Then a webhook is sent to all registered endpoints with payload including {tenant_id, event_type, active_version_id, previous_version_id, time_window, changed_themes_count, job_id, timestamp} And the webhook is signed (HMAC-SHA256) with a per-tenant secret and includes an idempotency key And failed deliveries are retried with exponential backoff for at least 24 hours and are viewable in a retry dashboard And a notification is emitted to in-app channels and email if enabled
Version-Aware Trendlines and Audit Annotations
Given score versions change over time When viewing trendlines for Theme Health Scores Then charts are rendered using the currently active version with version change annotations at the exact timestamps And users can toggle to any stored version to render the same trendline for comparability And the API provides a version parameter to fetch trendline data deterministically And an audit trail view lists all version changes with actor, reason, and affected scopes
Integration-safe Weight Propagation
"As a PM who syncs to Jira/Linear, I want anomaly-adjusted rankings and flags to carry through so that my external backlog reflects real priorities without manual reconciliation."
Description

Extends payloads for the ranked queue, API, webhooks, and one-click Jira/Linear sync to include anomaly metadata (type, confidence) and final adjusted weights while maintaining stable IDs and backward compatibility. Ensures quarantined items are configurable to exclude/include in sync, and that downstream tools receive consistent priority ordering. Provides schema documentation, versioned fields, and guardrails so external automations remain stable as Outlier Shield evolves. Validates end-to-end that adjustments in EchoLens are reflected accurately in external backlogs.

Acceptance Criteria
Ranked Queue API payload includes anomaly metadata and adjusted weights
Given an authenticated API client requests the ranked queue endpoint with schemaVersion=v2 When Outlier Shield has adjusted weights for items Then each item in the response includes fields: id (string), adjustedWeight (number), anomaly.type (DUPLICATE|BOT_FLOOD|VIRAL_THREAD|NONE), anomaly.confidence (number 0.0–1.0) And adjustedWeight reflects downweighting within ±0.001 of the internal value And id values are identical to pre-Outlier Shield ids for the same items And if schemaVersion is omitted, the response remains backward compatible and excludes new fields
Webhook events deliver versioned anomaly fields without breaking signatures
Given a subscribed webhook with secret S and preferredSchemaVersion=v2 When an event is sent for ranked_queue.updated Then the JSON payload contains anomaly.type, anomaly.confidence, adjustedWeight and a header X-Schema-Version=v2 And the HMAC signature header validates against S And a v1 subscriber (no version header) receives a valid payload that omits new fields and validates signatures
One-click Jira/Linear sync carries adjusted priority and anomaly context
Given connected Jira and Linear workspaces with default field mappings and schema v2 enabled When the user initiates one-click sync for the top 20 ranked items Then created/updated issues preserve the ranked order by adjustedWeight (strictly non-increasing from 1 to 20) And each issue stores adjustedWeight and anomaly.type as mapped fields or labels per integration And no duplicate external issues are created for items already linked (verified by stored externalIssueId) And retries occur up to 3 times on transient 5xx errors without reordering When an item’s adjustedWeight changes post-sync Then the corresponding external issue’s priority/fields update within 5 minutes without creating a new issue
Configurable inclusion of quarantined items in sync
Given quarantined items exist and IncludeQuarantinedInSync is OFF When a one-click sync is executed Then no quarantined items are created or updated in Jira/Linear And the ranked queue and API still include quarantined items with anomaly.type and adjustedWeight Given IncludeQuarantinedInSync is ON When a one-click sync is executed Then quarantined items are included in sync and labeled quarantined=true in external systems
Consistent priority ordering across UI, API, webhook, and external backlogs
Given a dataset of at least 50 items with mixed anomalies When the ranked view is generated in the EchoLens UI and exported via API and webhook, then synced to Jira and Linear Then the ordering of the top 50 items matches exactly across all surfaces by id And any two items A and B with adjustedWeight_A > adjustedWeight_B show A before B everywhere And ties within ±0.0005 maintain a stable secondary sort by id lexicographically
Schema documentation, versioning, and guardrails
Given documentation hosting is available When schema v2 is released Then a machine-readable JSON Schema and human-readable docs describe new fields, types, and enum values with examples And anomaly.* and adjustedWeight are optional in v2 and marked non-breaking And a contract test suite blocks removal or renaming of existing v2 fields in CI And a deprecation policy and timelines are published and linked via a response Link header rel="schema"
Backward compatibility with existing integrations
Given a client built against pre-Outlier Shield payloads (v1) When it consumes ranked queue responses and webhook events and triggers sync via existing automation Then it operates without errors or code changes And it receives identical id values and stable ordering relative to v1 semantics And any unknown fields are safely ignored by the client

Role Composer

Build and deploy least‑privilege roles in minutes with smart presets mapped to EchoLens user types (PM, CX, Exec, Admin). See exactly which actions each role unlocks before saving, get AI‑suggested permissions based on team activity and connected sources, and push mappings to SSO/SCIM in one click. Cuts onboarding time, prevents over‑permissioning, and keeps access consistent across teams.

Requirements

Smart Role Presets Library
"As an admin, I want to apply a preset role tailored to a user type so that onboarding is fast, consistent, and least‑privileged."
Description

Provide a curated library of least‑privilege presets aligned to EchoLens user types (PM, CX, Exec, Admin). Each preset maps to an explicit permission set across EchoLens capabilities (theme viewing, triage queue editing, source management, exports, Jira/Linear sync, theme model tuning, SSO/SCIM administration). Allow admins to clone, customize, version, and roll back presets, with clear labels and change notes. This accelerates onboarding, standardizes access, and reduces over‑permissioning while integrating seamlessly into the Role Composer UI and action catalog.

Acceptance Criteria
Curated Presets Library for EchoLens User Types
Given I am an Org Admin on Role Composer > Presets When I open the library Then I see four curated presets named PM, CX, Exec, and Admin And each preset shows a description, owner "EchoLens", version tag (>= v1.0), and last-updated timestamp And each preset explicitly maps every capability with an Allowed or Not Allowed state: theme viewing, triage queue editing, source management, exports, Jira/Linear sync, theme model tuning, SSO/SCIM administration And the default matrix is: - Exec: Allowed = theme viewing, exports; Not Allowed = triage queue editing, source management, Jira/Linear sync, theme model tuning, SSO/SCIM administration - PM: Allowed = theme viewing, triage queue editing, exports, Jira/Linear sync; Not Allowed = source management, theme model tuning, SSO/SCIM administration - CX: Allowed = theme viewing, triage queue editing, exports, Jira/Linear sync, source management; Not Allowed = theme model tuning, SSO/SCIM administration - Admin: Allowed = all listed capabilities And no capability is left unspecified or hidden
Permission Preview and Diff Before Save
Given I have selected a preset or customized configuration When I click Preview permissions Then a panel lists the exact actions unlocked per capability with Allowed/Not Allowed and action descriptions And a side-by-side diff highlights adds/removes versus the current role configuration And the panel displays total allowed permissions and number of changes (adds/removes) And the preview deterministically matches the permissions that will be saved (no discrepancy across subsequent save/load)
Clone and Customize Preset With Labels and Change Notes
Given I am viewing a curated preset When I click Clone Then a draft preset is created with editable name (3–50 characters) and default suffix "(Custom)" And Save is disabled until at least one label (1–3 tags) and change notes (>= 10 characters) are provided And I can toggle any capability; enabling a permission with prerequisites auto-enables required base permissions or shows a clear blocking message And on Save, version v1.0 is created under my org with creator, timestamp, and diff summary recorded And the preset card displays its labels and description after save
Version History and One-Click Rollback
Given a custom preset has two or more versions When I open Version History Then I see entries with version numbers, authors, timestamps, and diff summaries When I select a prior version and click Roll Back Then a new version is created restoring that snapshot and incrementing the version number And role assignments remain unchanged by the rollback And a required rollback reason is captured, an audit event is logged, and a success confirmation is shown And Roll Back is disabled if there are unsaved changes in the editor
Integration With Role Composer and Action Catalog
Given I am creating or editing a role in Role Composer When I apply a preset from the library Then the action catalog populates to exactly match the preset's permissions And dependency/conflict validation runs; unmet prerequisites are auto-enabled or clearly flagged with remediation steps And Save remains disabled while conflicts exist and enables once all are resolved And the UI updates real-time counts of allowed permissions and displays a risk level badge And switching to another preset prompts me to discard or merge current unsaved changes
Access Consistency and Least-Privilege Validation
Given I apply any curated preset without edits When I run the access validator Then zero high-risk over-permissioning findings are returned for the chosen preset And Exec preset enables 0 write actions (no triage edits, no source management, no model tuning, no SSO/SCIM admin) and allows view + exports only And PM and CX presets do not include SSO/SCIM administration or theme model tuning by default And Admin preset includes all capabilities and is flagged as elevated; applying it requires explicit confirmation
Permission Simulator & Impact Preview
"As a security‑minded admin, I want to preview the exact impact of a role before saving so that I can prevent unintended access."
Description

Enable an in‑editor simulator that previews exactly which UI sections, API endpoints, data objects, and data scopes become accessible with the current role configuration. Provide a diff view versus the user’s current role and versus a selected preset, along with human‑readable lists of unlocked actions. Flag and block dangerous combinations in real time (via guardrails) and ensure sub‑500ms response for previews. This helps admins validate intended access before saving and reduces risk of unintended exposure.

Acceptance Criteria
Real-Time Preview of Accessible UI, APIs, Data Objects, and Scopes
Given an admin is configuring a role in Role Composer When they add or remove any permission Then the simulator updates to list accessible UI sections, API endpoints, data objects, and data scopes reflecting the current configuration And each listed item includes a human-readable label and a unique identifier (route name, endpoint path, object name, scope) And category counts match the number of items displayed in each category And Given a baseline role with no permissions When previewed Then no accessible items are listed in any category
Diff Versus Current Role and Selected Preset
Given the admin opens the diff view When comparing against the user’s current role Then added, removed, and unchanged items are correctly categorized and counted for UI, API, data objects, and scopes And When switching comparison to a selected preset Then the diff recalculates and displays updated added/removed/unchanged sections within 500ms of selection And When the configured role matches the comparison target exactly Then all diff counts show zero and the UI indicates "No differences"
Human-Readable Unlocked Actions List
Given the simulator has generated a preview When viewing the Unlocked Actions list Then each action is written in verb + object form (e.g., "View tickets", "Export analytics reports") with no internal permission codes displayed And each action maps to at least one underlying permission id available via tooltip or details And selecting an action highlights its corresponding UI/API/data entries in the preview And the list supports search filtering by keyword and returns matching actions in under 200ms for queries up to 50 characters
Guardrails: Detect and Block Dangerous Permission Combinations
Given guardrail rules are configured for dangerous combinations (e.g., "Export PII" + "Delete Records" or "Org-wide read" + "External share") When the current role configuration matches any guardrail rule Then a blocking banner with severity level is displayed within 300ms, Save/Sync actions are disabled, and a descriptive remediation list is shown And When the configuration no longer violates any guardrail Then the banner disappears and Save/Sync actions re-enable immediately And any guardrail trigger is logged with rule id, role id, and timestamp
Performance: Sub-500ms Preview Response
Given instrumentation is enabled for preview requests When measuring end-to-end time from user input to fully rendered updated preview Then p95 latency is <= 500ms and p99 latency is <= 800ms over a rolling 15-minute window under nominal load (<= 50 concurrent composers per shard) And server-side preview computation p95 is <= 300ms And metrics are exposed via /metrics with p50/p95/p99 and error rate
Error Handling and Non-Persistence on Failure
Given the preview API returns an error or times out after 800ms When the admin modifies permissions Then the UI shows a non-blocking error state with Retry, Save/Sync remain disabled, and no role changes are persisted And the last successful preview (if any) remains visible and is clearly marked as Stale And upon a successful retry, the error state clears and Save/Sync are re-enabled subject to guardrails
Scope-Level Accuracy Verification via Dry-Run Access Checks
Given a configured role draft in the simulator When running automated dry-run access checks against a sampled set of 50 UI routes, 50 API endpoints, and 20 data objects across read/write/delete/export scopes Then simulator-predicted access must match dry-run results with >= 99% overall accuracy and 0 critical mismatches (over-grants) allowed And any mismatch produces a diff report listing the item, expected, actual, and mapping source
AI Permission Suggestions with Explainability
"As an admin, I want AI to suggest least‑privilege permissions based on how my team actually works so that users get only the access they need."
Description

Leverage activity signals, connected sources, and historical assignment patterns to recommend the minimal set of permissions for a given user or group. Provide per‑permission confidence scores and natural‑language rationale citing observed behaviors (e.g., frequent triage edits, no export history). Suggestions are never auto‑applied; admins can accept, reject, or adjust them individually. Respect data privacy constraints and keep all processing within approved boundaries. This shortens setup time and curbs over‑permissioning.

Acceptance Criteria
Per-permission Confidence Scores and Rationale Display
Given a target user or group with at least one connected source and recent activity When the admin opens Role Composer > AI Suggestions Then each suggested permission displays permission name and id, a confidence between 0.00 and 1.00 with two decimals, and a natural-language rationale <= 200 characters that references at least one observed behavior or connected source And each suggestion lists at least one top contributing signal with its weight And any suggestion lacking both a confidence score and rationale is not rendered
Explicit Admin Control; No Auto-Apply
Given suggestions have been generated When no admin action is taken Then no permission changes are applied to any role or user When the admin clicks Accept on a permission Then only that permission is added to staged changes for the selected assignee When the admin clicks Reject on a permission Then that suggestion is removed from the list and not added to staged changes When the admin clicks Adjust on a permission Then the admin can modify scope (e.g., read/write) and the displayed confidence is recalculated within 1 second When the admin clicks Save Then only staged changes are persisted and an audit log entry is written with actor, assignee, permission ids, actions (accepted/rejected/adjusted), timestamp, and request id
Privacy Boundary and Residency Enforcement
Given tenant data residency is configured (e.g., EU or US) When suggestions are generated Then all processing occurs within the configured region and no PII is transmitted to unapproved endpoints And calls to unapproved external AI endpoints are blocked and logged with reason And signal detail views mask end-user identifiers unless the viewer has Masking Override permission When an unapproved data source is connected Then it is ignored for suggestions and a warning banner indicates it is out of scope
Signal Coverage: Activity, Connected Sources, and History
Given activity events, connected source metadata, and historical assignment patterns exist for the subject When generating suggestions Then any permission with confidence >= 0.60 cites at least one signal from each available category (activity, connected sources, history) in its explanation And dependent permissions required for accepted suggestions are also suggested and labeled as Dependency And no permission with confidence < 0.40 is suggested unless it is a required dependency, in which case it is grouped under the parent
Insufficient Data Handling
Given fewer than 10 relevant activity events in the last 30 days and no connected sources When generating suggestions Then the interface displays an Insufficient Data state with guidance to connect sources or extend lookback And no high-risk permissions (Export Data, Manage Billing, Delete Organization) are suggested And a reason badge is shown on suppressed permissions explaining low-signal suppression
Latency and Availability
Given standard load (P95: 50 concurrent admins requesting suggestions) When an admin requests suggestions Then first paint of suggestions occurs within 2.0 seconds at P95 and 4.0 seconds at P99 And expanding rationale/details for any suggestion opens within 200 ms at P95 And timeouts return an explicit error with retry-after and are recorded for SLO tracking
Traceable Explainability Details
Given a suggestion is displayed When the admin clicks View Details Then the system shows the top 10 contributing events with source, timestamp, and weight for that permission And each sentence in the rationale links to at least one underlying event or assignment pattern And details can be exported as CSV including permission id, confidence, contributing signals, model version, and dataset hash
One‑click SSO/SCIM Role Sync
"As an IT admin, I want to push role mappings to our IdP in one click so that access remains consistent across EchoLens and our identity stack."
Description

Provide seamless synchronization of EchoLens roles and assignments to IdPs (Okta, Azure AD, Google Workspace) using SCIM 2.0. Map EchoLens roles to IdP groups, support just‑in‑time provisioning/deprovisioning, and implement idempotent sync with retries, conflict resolution, and detailed error reporting. Include a dry‑run mode, webhook callbacks for status, and a health dashboard. This keeps access consistent across systems and reduces IT overhead.

Acceptance Criteria
One-Click Role-to-Group Sync (Okta, Azure AD, Google)
Given EchoLens roles are mapped to corresponding IdP groups for Okta, Azure AD, and Google Workspace And the integration has valid SCIM 2.0 credentials with required scopes When an Admin clicks "Sync Now" in Role Composer Then the system creates or updates SCIM groups (if missing) and aligns group memberships to match EchoLens role assignments And returns a success summary including per-IdP counts of created/updated/unchanged members and groups And completes within p95 <= 2 minutes for directories up to 10,000 users And writes an immutable audit log entry with actor, correlation_id, per-IdP results, and timestamp
Just-in-Time Provisioning and Deprovisioning via SCIM
Given a user is assigned to or removed from a mapped IdP group When the IdP sends a SCIM User/Group update to EchoLens or EchoLens polls per configuration Then the user is provisioned with the corresponding EchoLens role within p95 <= 60 seconds of the IdP event And when removed from the mapped group, the user is deprovisioned in EchoLens within p95 <= 60 seconds and active sessions are revoked within <= 30 seconds And when the IdP marks a user as suspended, the EchoLens account is disabled within p95 <= 60 seconds And if the incoming group is unmapped, no role is granted and the event is logged with severity=warning
Idempotent Sync with Retries and Backoff
Given a sync job with correlation_id X is initiated When transient errors (HTTP 429/5xx, network timeouts) occur Then the system retries with exponential backoff up to 5 attempts per endpoint and uses idempotency keys to avoid duplicate creates And the final state after any number of retries is identical to a single successful run (no duplicate users, groups, or memberships) And permanent 4xx errors (e.g., 400, 403, 404) are not retried and are surfaced in the job report And partial progress is checkpointed so a subsequent run resumes from the last consistent state
Conflict Resolution and Source-of-Truth Rules
Given EchoLens local assignments differ from IdP group memberships for the same user When a sync runs Then IdP group membership is treated as the source of truth and local assignments are updated to match And if a group is mapped to multiple EchoLens roles, the sync is blocked, the job fails with conflict code EL-RSYNC-409, and no changes are applied And if the same EchoLens role is mapped to multiple IdP groups, the effective membership is the union of those groups And all conflicts are included in the job report with user/group identifiers and remediation guidance
Dry-Run Mode with Detailed Diff and No Side Effects
Given an Admin selects Dry Run for a chosen IdP and role mappings When the dry-run job executes Then no writes occur to the IdP or EchoLens And a diff report is generated including counts of users to add/remove/update, groups to create/link, and sample member lists (max 100 per category) And the report is exportable as JSON and CSV and viewable in-app And preflight checks validate API scopes, rate limits, and unmapped roles, failing the dry run with explicit error codes if prerequisites are unmet
Webhook Callbacks for Sync Lifecycle and Errors
Given a webhook endpoint with a shared secret is configured When a sync job transitions states (queued, running, succeeded, failed, partial) Then a signed callback is sent within <= 10 seconds including event_type, correlation_id, idempotency_key, started_at, finished_at, per-IdP metrics, and error details if any And deliveries are retried up to 8 times with exponential backoff on 429/5xx; after exhaustion, events move to a dead-letter queue visible in the dashboard And each payload is HMAC-SHA256 signed; receivers can verify signature and timestamp to prevent replay
Health Dashboard and Observability
Given an Admin opens the Health dashboard When viewing connected IdPs Then the dashboard displays last sync time, last status, p50/p95/p99 sync latency, success/failure counts for the past 30 days, top error codes, and token expiry dates And shows real-time sync queue depth and current job progress with percentage and ETA And supports filtering by IdP and date range, and export of logs as NDJSON And alerts can be configured to send email and webhook notifications on job failure, error rate > 5% over 15 minutes, or token expiry within 7 days
Change Audit Trail and Approval Workflow
"As a compliance officer, I want audited and approvable role changes so that we meet regulatory requirements and can trace access decisions."
Description

Record immutable, searchably indexed audit logs for role creation, edits, assignments, and external sync events, including actor, timestamp, origin IP, and before/after diffs. Support optional two‑person approval (maker‑checker) before changes take effect, with notifications and SLAs. Provide exportable reports and retention controls to meet compliance needs. Surface history within Settings > Security and expose via API for SIEM ingestion.

Acceptance Criteria
Immutable Audit Logging for Role Lifecycle Events
Given a user creates, edits, assigns, or externally syncs a role via UI, API, or SCIM When the action is submitted Then an audit entry is appended with non-null fields: actor_id, actor_email, action_type, target_type, target_id, timestamp_utc (ISO-8601), origin_ip, request_id, source_channel (UI/API/SCIM), outcome (success/fail), before_diff, after_diff Given any user attempts to modify or delete an existing audit entry via UI or API When the request is executed Then the system rejects the operation and the entry remains unchanged; an additional audit entry records the denied attempt Given an export of audit entries is produced When entry checksums/tamper-evidence are validated Then all entries verify as untampered
Indexed Search and Filter of Audit Log
Given 50k+ audit entries exist When a user filters by actor, action_type, target_id, origin_ip, outcome, or timestamp range, or searches for a substring present in before/after diffs Then the first page of results for a 25-row page returns within 2 seconds, ordered by timestamp descending, with only matching entries shown Given a query has no matches When the query is executed Then an empty result set is returned without error Given the user paginates through results using a stable cursor When next pages are requested while new entries are being written Then no duplicates or gaps occur across pages
Two‑Person Approval (Maker‑Checker) Enforcement
Given the organization setting "Two-person approval" is enabled When a maker submits a role change (create/edit/assignment/mapping sync) Then the change enters Pending status, effective permissions remain unchanged, and an approval request with an SLA deadline is created and linked to the change Given the maker attempts to approve their own request When they try to approve Then the system blocks the action and records the attempt in the audit log Given an authorized approver approves before the SLA deadline When they approve Then the change is applied atomically, effective permissions update, and both approval and application are recorded with before/after diffs Given the SLA deadline elapses without approval When the deadline passes Then the request automatically expires without applying the change, and notifications are sent to maker and approvers
Approval Notifications and SLA Escalation
Given an approval request is created When the request is submitted Then approvers receive in-app and email notifications within 1 minute containing maker identity, action summary, diff summary, and SLA deadline with a deep link to approve or reject Given a pending request approaches SLA thresholds When 50% of SLA has elapsed and again 10 minutes before SLA expiry Then reminder notifications are sent to approvers and recorded in the audit log Given a request is approved or rejected When the decision is recorded Then the maker receives an in-app and email notification within 1 minute, including the decision, timestamp, approver identity, and rejection reason (if applicable)
Exportable Reports and Data Retention Controls
Given a permitted user requests an audit export with selected filters and time range When the export is initiated Then CSV and JSON files are generated asynchronously with column headers and required fields, chunked for large datasets, available within 5 minutes for typical loads, and the export event is logged Given the organization sets a retention period of N days with no legal hold When the nightly retention job runs Then audit entries older than N days are purged, the purge summary is logged, and searches/exports do not return purged data Given legal hold is enabled for the audit log When the retention job runs Then entries under legal hold are retained regardless of age until the hold is lifted Given an export is downloaded When file integrity is verified using the provided checksum Then the checksum matches and the file parses without errors
In‑App Audit History in Settings > Security
Given an Admin or Security Approver navigates to Settings > Security > Audit Trail When the page loads with 10k+ entries present Then the table renders within 2 seconds showing columns for actor, action, target, timestamp, origin_ip, source_channel, outcome, and supports filter and search controls Given a user without the required permission attempts access When they navigate to the Audit Trail Then access is denied (403/unauthorized view) and no audit data is exposed Given a user opens an audit entry When the details view is opened Then the full before/after diff, related approval request (if any), and raw payload metadata are displayed
Audit Log API for SIEM Ingestion
Given a SIEM client authenticates with a token that has audit.read scope When it calls GET /audit-logs with filters (actor, action_type, target_id, outcome, origin_ip), a since timestamp, and a limit Then the API responds 200 with items[] containing the required fields, ordered by timestamp descending then id, and a next_cursor for pagination Given the client follows next_cursor for subsequent pages while new entries are written When requesting the next pages Then no records are duplicated or skipped, and pagination remains consistent Given since predates the retention window When the request is executed Then only logs within the retention window are returned and the response includes retention_window metadata Given a client without audit.read scope calls the endpoint When the request is made Then a 403 is returned and the access attempt is recorded in the audit log
Least‑Privilege Guardrails & Policy Linter
"As a security admin, I want guardrails that lint role policies in real time so that risky configurations are prevented or justified."
Description

Implement a real‑time rules engine that evaluates role configurations against org policies and best practices. Flag over‑broad permissions (e.g., write across all workspaces), PII exposure without justification, and unscoped integration access. Offer actionable remediation suggestions and require documented justification for exceptions. Admins can set policies to warn or enforce (block save). This consistently enforces least‑privilege and reduces risk.

Acceptance Criteria
Real-Time Linting During Role Editing
Given an admin is editing a role in Role Composer When any permission is added, removed, or scoped Then the linter re-evaluates and updates the violation list and badge counts within 300 ms for 95% of interactions and within 500 ms worst case, without blocking typing Given the linter completes evaluation When violations exist Then each violation displays severity (Warn/Enforce), code, message, and an "Apply fix" action if available
Block Save on Over-Broad Permissions (Enforce Mode)
Given org policy "Workspace Write Scope" is set to Enforce And the role grants Write across All Workspaces When the admin clicks Save Then the save is blocked, the Save button remains disabled, and an error banner with code POL-OVERBROAD-001 is displayed until the permission is scoped to at least one workspace or removed
Warn vs Enforce Policy Modes Behavior
Given a policy violation exists for the current role When the policy is set to Warn Then Save succeeds, a non-blocking warning banner with the violation code and count is shown, and an audit event "policy_warn_saved" is recorded with role_id and actor_id Given the same violation When the policy is set to Enforce Then Save is blocked and an audit event "policy_enforce_block" is recorded with role_id and actor_id
PII Exposure Justification Workflow with Audit Trail
Given the role includes any permission tagged PII (e.g., View PII fields, Export conversations) And org policy "PII Exposure" is set to Enforce When the admin attempts to Save Then the system requires an exception justification: free-text reason (minimum 20 characters), reason code from controlled list, exception owner, and expiration date; Save remains blocked until all fields are provided Given a valid justification is submitted When Save completes Then an exception record with code POL-PII-002, timestamps, actor/approver, expiration, and diff of granted permissions is stored and visible in Audit Log and role history
Unscoped Integration Access Detection & Auto-Scope Fix
Given the role grants any Integration permission unscoped across all connected sources or environments When the linter runs Then a Critical violation POL-INTEG-003 is raised and at least one auto-fix is proposed: add source scoping, restrict to Read, or remove permission Given the admin applies the suggested "Add source scoping" fix When the linter re-evaluates Then the violation is resolved and the updated permission shows selected sources; no new Critical violations are introduced
Actionable Remediation Suggestions Apply Cleanly
Given a violation has an available auto-fix When the admin clicks "Apply fix" Then the role configuration updates, the fix is reversible via "Undo" within the session, and the violation count decrements by one Given multiple violations exist When "Apply all fixes" is invoked Then all fixes apply atomically; on any error, no partial changes are saved and a rollback occurs with error code POL-FIX-ROLLBACK
SSO/SCIM Push Honors Guardrails
Given the admin attempts "Push mappings to SSO/SCIM" And any Enforce-mode violations are present When Push is initiated Then the push is blocked, a modal lists blocking violations with codes, and no outbound calls are made to the IdP Given violations are resolved or justified per policy When Push is re-attempted Then the push succeeds and an audit event "scim_push_success" includes the linter summary hash for traceability
Scoped Access and Time‑bound Entitlements
"As a team lead, I want to scope and time‑limit access for specific projects and sources so that temporary needs don’t become permanent risks."
Description

Allow permissions to be scoped by workspace, data source, team, and environment (prod/sandbox), with optional time‑boxed access that auto‑expires and notifies owners. Provide bulk assignment tools, APIs, and UI filters, ensuring enforcement across UI, API, exports, theme map, and triage queue. This supports contractors, special projects, and temporary escalations without permanent over‑permissioning.

Acceptance Criteria

Just‑in‑Time Elevate

Grant time‑boxed, reason‑coded access for a specific task (e.g., merge a top theme or edit scoring) with approver sign‑off via Slack/SSO. Elevation auto‑expires, reverts cleanly, and is fully logged to Integrity Chain and Reason Codes. Minimizes standing admin rights while unblocking urgent work without risk.

Requirements

Scoped Task‑Based Elevation
"As a product manager blocked from merging themes, I want to request narrow, task‑specific elevated access so that I can complete the merge without broad admin privileges."
Description

Provide granular, least‑privilege elevation scopes tied to specific tasks within EchoLens (e.g., merge themes, edit scoring weights, reclassify clusters, approve ranked queue changes). Requesters select scope, targeted workspace/project, resource identifiers, and allowed actions; the system issues a short‑lived elevation token bound to the user session and enforces guardrails to prevent access outside the declared scope. UI entry points on Theme Map and Ranked Queue surface an Elevate action when permissions are insufficient, including a read‑only preview of the proposed change. Predefined scopes are available out of the box, with admin‑configurable custom scopes to align with internal policies. All elevated API calls are tagged for traceability and respect existing rate limits and conflict resolution. Benefits include reduced standing admin rights, faster unblock for urgent work, and minimized blast radius.

Acceptance Criteria
Approver Sign‑off via Slack/SSO
"As an approver on‑call, I want to approve or deny elevation requests directly from Slack or an SSO page so that urgent tasks are unblocked quickly with full auditability."
Description

Enable approver workflows through Slack interactive messages and a secure SSO approval page. Each request includes requester identity, task scope, reason code, target resources, and requested duration, with Approve/Deny and optional comment. Policies map workspaces and scopes to primary and backup approvers, enforce requester ≠ approver, and support multi‑approver when required. Slack integration uses enterprise OAuth with signed interactions; SSO approvals capture approver identity via SAML/OIDC assertions. Support expirations of pending requests, reminders, and escalation to backup approvers. All decisions are recorded for audit and surfaced in activity feeds.

Acceptance Criteria
Time‑Boxing, Auto‑Expiry, and Clean Reversion
"As a security lead, I want elevated access to auto‑expire and revert cleanly so that risk is minimized and no residual privileges persist after the task is done."
Description

All elevated access is time‑boxed with mandatory duration selection and hard auto‑expiry. On expiry, elevation tokens are revoked, user sessions return to baseline permissions, and any temporary toggles revert to their prior state. Operations initiated under elevation use transactional commits or compensating actions to ensure safe rollback; in‑flight actions are allowed to complete or are aborted consistently based on idempotent semantics. Revocation and rollback events are generated for monitoring, and background jobs spawned under elevation are constrained to the elevation window and scope. Configuration supports maximum durations per scope and workspace.

Acceptance Criteria
Reason Codes and Policy Validation
"As a compliance officer, I want reason codes captured and validated against policy so that we can enforce governance and analyze why elevations occur."
Description

Capture structured reason codes for every elevation, including category, free‑text justification, and links to Jira/Linear tickets. Validate requests against policy rules (e.g., permitted scopes for incident severity, maximum duration by reason, required ticket linkage) and block or warn when misaligned. Enforce minimum justification length and impact area selection for downstream analytics. Reason data is stored with the request and attached to all subsequent elevated actions for traceability and reporting.

Acceptance Criteria
Integrity Chain Audit Logging and Export
"As an auditor, I want a tamper‑evident log of all elevated actions with export options so that I can verify controls and investigate incidents when needed."
Description

Record the full elevation lifecycle to the Integrity Chain: request submission, approvals/denials, token issuance, actions performed under elevation (e.g., merged theme IDs, scoring changes), expiry, and rollback results. Each entry includes timestamps, user and approver identities, scope details, reason codes, session identifiers, and cryptographic hash chaining to make the log tamper‑evident. Provide filtered audit views in‑app, plus export to CSV/JSON and webhook delivery to SIEM tools for compliance and forensics.

Acceptance Criteria
Notifications and Monitoring Dashboard
"As a team lead, I want notifications and a monitoring view of active elevations so that I can maintain oversight and prevent misuse or forgotten sessions."
Description

Deliver real‑time notifications for new requests awaiting approval, impending elevation expirations, denials, and anomalous patterns (e.g., repeated requests or policy violations). Channels include Slack, email, and in‑app banners showing active elevation status and remaining time. Provide a monitoring dashboard with active elevations, time remaining, scope, requester, and approver, plus weekly trend reports by reason code and scope. Thresholds and recipients are configurable per workspace; alerts link directly to audit entries and request details.

Acceptance Criteria

Scope Fencing

Fence access by source, theme group, segment, and even field level with built‑in PII masking and export controls. Enforce data residency and customer/tenant isolation so CX sees only what they need, alerts redact consistently, and exports/webhooks can’t leak sensitive data. Reduces compliance risk without slowing daily workflows.

Requirements

Multi-Dimensional Scope Policies
"As a CX admin, I want to restrict access by source, theme, segment, and specific fields so that agents only see relevant data and sensitive information remains protected."
Description

Implement attribute-based access control (ABAC) that enforces deny-by-default policies across source (reviews, emails, tickets, chats), theme group, segment, and field-level scopes. Policies must be evaluated consistently at query time and at every egress surface (UI, API, exports, webhooks, alerts). Support hierarchical inheritance (org > workspace > team > user), exception rules, time-bounded elevations, and policy caching with immediate invalidation on change. Integrate with SSO/SCIM groups for role mapping and with the data layer using row-level and column-level security. Provide full audit of policy evaluations.

Acceptance Criteria
PII Masking and Redaction Engine
"As a security officer, I want PII to be automatically detected and consistently redacted across all surfaces so that we prevent leakage without slowing daily workflows."
Description

Provide a centralized masking engine that detects common PII (emails, phone numbers, addresses, names, IDs) using a configurable pattern library and allows custom rules. Apply deterministic, format-preserving redaction at read time across UI, search index, alerts, exports, and webhooks, with consistent tokens for cross-surface traceability. Support role-based reveal for privileged users, field-level exemptions with justification, and per-tenant configurations. Ensure masked data is logged and audited without storing raw PII in logs.

Acceptance Criteria
Tenant and Customer Data Isolation
"As a workspace admin, I want hard tenant and customer isolation so that no user can access another tenant’s or customer’s data directly or indirectly."
Description

Enforce strict tenant and customer isolation using workspace-bound encryption keys, schema or row-level separation, and service-layer guards that prevent cross-tenant queries, background jobs, and caches from mixing data. Ensure all derived artifacts (embeddings, indices, aggregations) inherit the same isolation. Include automated regression tests and canary checks to prove isolation on every deploy, and provide incident tooling to verify suspected leakage.

Acceptance Criteria
Data Residency Enforcement
"As a compliance manager, I want data to remain within its assigned region and prevent cross-border egress so that we meet regulatory and contractual obligations."
Description

Implement per-tenant region locks for storage, processing, and indexing so that data never leaves its designated geography. Route ingestion, compute, and notifications to region-local infrastructure; block or rewrite exports and webhooks that target out-of-region destinations. Support region-tagged sources, fail-closed behavior on misconfiguration, residency-aware backups/DR, and auditable evidence for compliance reviews.

Acceptance Criteria
Policy-Aware Exports and Webhooks
"As an integration owner, I want exports and webhooks to honor scope and masking policies so that sensitive data never leaks to external systems."
Description

Make all exports and webhooks policy-aware by evaluating scope and masking rules before payload creation. Redact or drop fields and records that violate policies, enforce destination allowlists/denylists, and require destination metadata (region, vendor) for compliance checks. Provide signed delivery, retry with backoff, rate limiting, payload previews, and immutable audit logs capturing who initiated, what was sent, applied redactions, and delivery outcomes.

Acceptance Criteria
Consistent Alert Redaction
"As a team lead, I want alerts to automatically redact sensitive content per recipient’s permissions so that notifications remain useful without exposing PII."
Description

Generate channel-specific alerts (email, Slack, etc.) that dynamically apply each recipient’s scope and masking rules, ensuring content is useful while sensitive fields are redacted in a consistent, deterministic manner. Include visual redaction indicators, safe snippets, and links that deep-link to an already-filtered view. Provide an admin preview mode and automated tests to prevent accidental leakage through templating.

Acceptance Criteria
Policy Management UI and API
"As an admin, I want a UI and API to define and simulate scope fencing policies so that I can manage rules safely without blocking daily workflows."
Description

Deliver an admin UI and REST API to create, version, simulate, and deploy scope and masking policies. Include a policy simulator (who can see what and why), dry-run impact analysis, conflict detection, approvals, changelogs, and rollback. Support SCIM-based group sync, import/export of policies as code, and webhooks for policy change events, with fine-grained permissions for policy authors vs approvers.

Acceptance Criteria

Privilege Guard

Continuously analyzes who has what and how it’s used to spot privilege creep, template drift, and risky combinations. Surfaces unused rights, highlights deviations from approved role baselines, and recommends one‑click right‑sizing or auto‑remediation. Keeps access tight and audit‑ready with proactive alerts.

Requirements

Unified Permission Inventory
"As a workspace admin, I want a real-time, normalized view of who has what across EchoLens so that I can spot over-provisioning and satisfy audits quickly."
Description

Continuously aggregates and normalizes all user and group entitlements across EchoLens (workspace, project, data source, export, and integration scopes) into a single, queryable inventory. Ingests from internal RBAC, SSO/IdP (e.g., Okta, Google, Azure AD) and SCIM where available, de-duplicates overlapping grants, and timestamps sources of truth. Maintains historical snapshots for audit trails and change detection. Exposes a minimal-permissions data model, APIs, and UI views to support analysis, reporting, and remediation workflows while respecting tenant boundaries and privacy controls.

Acceptance Criteria
Usage Telemetry Correlation
"As a security-conscious admin, I want to see which permissions are actually used so that I can safely remove unused access without breaking workflows."
Description

Captures fine-grained usage telemetry for permission checks and sensitive actions (e.g., managing connectors, exporting data, creating external tickets, editing role templates) and correlates events to specific entitlements. Computes unused-rights candidates based on configurable lookback windows and thresholds, with noise reduction for scheduled jobs and service accounts. Provides dashboards and APIs to surface per-user and per-permission utilization, with PII minimization, retention controls, and opt-in sampling for high-volume tenants.

Acceptance Criteria
Baseline Role Templates & Drift Detection
"As a compliance lead, I want to enforce approved role baselines and be alerted to deviations so that we remain audit-ready and minimize privilege creep."
Description

Enables definition and versioning of approved role templates with explicit permission sets and risk ratings. Supports assignment of baselines to users, groups, and projects, including exceptions with justification and expiry. Continuously compares effective entitlements against baselines to detect privilege creep and template drift, generating prioritized findings with context (who, what changed, when, why) and suggested corrective actions.

Acceptance Criteria
Risky Combination Detection
"As a product owner, I want to be alerted to risky permission combinations so that I can prevent data exfiltration and harmful misconfigurations."
Description

Provides a rule engine and catalog to detect toxic permission combinations and segregation-of-duties violations (e.g., export PII + manage connectors + delete data). Ships with opinionated defaults mapped to EchoLens scopes and allows custom rules with severity, rationale, and suppression windows. Evaluates changes in real time, annotates findings with impacted resources and users, and feeds a risk score used for prioritization and alerting.

Acceptance Criteria
One-Click Right-Sizing
"As an admin, I want one-click options to right-size access with clear impact previews so that I can remediate quickly and confidently."
Description

Surfaces actionable recommendations to remove unused permissions, downgrade roles, or align users to closest baseline with a single confirmation. Includes impact analysis (affected workflows, API tokens, scheduled jobs), dry-run previews, batch selection, and instant rollback. Requires optional peer or owner approval based on policy, writes a tamper-evident audit log, and updates both EchoLens RBAC and connected IdP/SCIM where configured.

Acceptance Criteria
Auto-Remediation Policies & Proactive Alerts
"As a CX lead with limited time, I want automated alerts and optional auto-revocation after inactivity so that access stays tight without constant manual reviews."
Description

Introduces configurable policies to auto-revoke or downgrade access when conditions are met (e.g., 30 days of non-use, expired exceptions, detected toxic combos), with guardrails such as require-approval and maintenance windows. Delivers proactive alerts via email and Slack, supports digesting and rate limits, and streams findings to SIEM/webhooks. Produces audit-ready reports and evidence exports (CSV/PDF) with before/after snapshots and policy rationale.

Acceptance Criteria

Access Recertify

Run scheduled access reviews with role owners and managers: attest, revoke, or downgrade in bulk with clear diffs and usage context. Sends reminders, escalates overdue tasks, and produces exportable evidence packs aligned to SOC 2/ISO controls. Turns painful quarterly reviews into a 30‑minute routine.

Requirements

Scheduled Review Cadence Engine
"As a compliance owner, I want to schedule recurring access reviews with defined scopes and due dates so that reviews happen consistently without manual coordination."
Description

Implements configurable, recurring access review campaigns (e.g., quarterly, monthly) with timezone-aware start/end dates, grace periods, and escalation thresholds. Supports scoping by workspace, application, group, and role, and freezes a baseline snapshot at campaign start to power change diffs. Provides campaign templates, cloning, and API endpoints so PMs/CX leads can standardize governance runs across teams. Aligns with EchoLens’s lightweight ethos by minimizing setup to a few clicks while ensuring multi-tenant isolation and secure data handling. Expected outcome: predictable, automated review cycles that reduce manual orchestration and make audits repeatable.

Acceptance Criteria
Role Owner & Manager Mapping with Delegation
"As a role owner, I want access items assigned to the correct owner or manager with the option to delegate so that each entitlement is reviewed by the most accountable person."
Description

Resolves the correct reviewer for each access item by mapping application role owners and employee managers via IdP/HRIS (e.g., Okta, Azure AD, Google Workspace, Workday). Supports fallback rules (e.g., security admin), delegation with acceptance, and out-of-office reassignment while preserving accountability. Enforces separation-of-duties constraints and prevents reviewers from attesting to their own elevated access. Integrates into campaign creation so each entitlement has a clear, accountable approver. Expected outcome: accurate routing of attestations to the right people, reducing cycle time and rework.

Acceptance Criteria
Bulk Attestation with Diff & Context View
"As a reviewer, I want to see access changes and usage context in one place and take bulk actions so that I can complete reviews quickly and accurately."
Description

Provides a review workspace that shows clear before/after diffs since the last campaign (new users, removed users, permission level changes) with batch selection to attest, revoke, or downgrade at scale. Includes pre-flight impact simulation, mandatory reason codes for revokes/downgrades, inline comments, and decision timestamps. Supports keyboard shortcuts, quick filters (dormant, high-risk, privileged), and confidence badges to accelerate decisions. Integrates with provisioning connectors to execute approved changes or open tickets when direct write is disabled. Expected outcome: reviewers complete comprehensive reviews in minutes with fewer errors.

Acceptance Criteria
Usage & Risk Context Enrichment
"As a security analyst, I want usage and risk signals shown for each entitlement so that I can make informed keep/revoke decisions."
Description

Aggregates signals like last login, MFA status, API token usage, resource access, ticket commits, and inactivity windows from connected systems to surface dormant or risky accounts. Normalizes and scores signals, displays them alongside each entitlement, and supports exceptions (service accounts, contractors) with policy notes. Ensures data freshness SLAs and gracefully degrades when signals are stale. Expected outcome: higher-quality decisions backed by evidence, reducing over-provisioning and audit findings.

Acceptance Criteria
Automated Reminders & Escalations
"As a compliance coordinator, I want automated reminders and escalations so that reviews are completed on time without manual follow-up."
Description

Delivers configurable reminder schedules via email and Slack/Teams, with nudges before due dates and auto-escalations to next-level managers or compliance leads when overdue. Tracks opens, clicks, and completion, prevents notification fatigue with throttling, and supports digest notifications. Exposes real-time campaign progress dashboards and webhooks for external tracking. Expected outcome: timely completion of reviews with minimal manual chasing.

Acceptance Criteria
Compliance Evidence Pack Generator (SOC 2/ISO)
"As an auditor, I want standardized evidence packs mapped to SOC 2/ISO controls so that I can verify access reviews without bespoke data requests."
Description

Produces exportable evidence packs (PDF/CSV/JSON) containing campaign scope, baseline snapshot hash, reviewer identities, decisions, timestamps, reasons, and change execution results. Maps evidence to SOC 2 (e.g., CC6.1, CC6.2) and ISO 27001 controls (e.g., A.5, A.9), includes digital signatures and tamper-evident hashes, and stores artifacts in encrypted, access-controlled archives with retention policies. Provides auditor-friendly summaries and drill-through links to raw logs. Expected outcome: audit-ready documentation generated instantly at campaign close.

Acceptance Criteria
Provisioning & Directory Connectors for Enforcement
"As an IT administrator, I want secure connectors that apply revoke and downgrade actions so that review decisions are enforced consistently across systems."
Description

Integrates with IdPs and targets (Okta, Azure AD, Google Workspace, SCIM 2.0 apps) to execute revoke/downgrade decisions in bulk with idempotency, retries, and rate-limit handling. Supports dry-run mode, granular least-privilege API scopes, secrets management, and writeback of execution status to the campaign. Falls back to ticket creation in Jira/Linear when direct enforcement is not permitted. Expected outcome: closed-loop governance where decisions reliably translate into access changes.

Acceptance Criteria

Policy Simulator

Safely preview what a user can see and do before changing a role or policy. Impersonate in a sandbox, dry‑run Change Gate rules, and view a clear list of allowed/blocked actions and data exposure. Identifies conflicts and suggests least‑priv alternatives, preventing lockouts and surprise escalations.

Requirements

Impersonation Sandbox Mode
"As a workspace admin, I want to impersonate a user in a sandbox so that I can safely verify what they can see and do before changing their role."
Description

Provide a safe, isolated simulation environment that lets admins preview a specific user’s effective permissions without changing production roles or policies. A simulation session applies the target identity (user, group, or API token) and policy set to all authorization checks, while mutating operations are converted to no-ops and side effects (notifications, webhooks, analytics) are suppressed. Sessions are time-bound, clearly labeled in the UI, and can be scoped to workspace, team, project, or data source level. Supports selecting data context (e.g., staging or a production snapshot) and ensures that all service calls honor the simulation context end-to-end. Integrates with EchoLens surfaces (Theme Map, Ranked Queue, Jira/Linear sync flows) so admins can navigate the product exactly as the impersonated user would, without risk.

Acceptance Criteria
Permissions and Action Matrix Preview
"As a security engineer, I want a clear list of allowed and blocked actions with reasons so that I can quickly verify policy intent and identify gaps."
Description

Generate a comprehensive, filterable matrix of allowed and blocked actions for the impersonated identity across EchoLens features and resources, including viewing the Theme Map, accessing ranked queues, exporting data, editing themes/tags, managing integrations, and initiating Jira/Linear syncs. For each action-resource pair, present decision rationale (policy, role, condition matched, and any explicit deny) with a readable evaluation trace. Provide resource scoping (workspace, team, project, customer segment), bulk search, and export to CSV/PDF for review. Support a diff view to compare current vs. proposed policy changes, highlighting net-new grants and newly blocked actions.

Acceptance Criteria
Change Gate Rules Dry-Run
"As a policy owner, I want to dry-run Change Gate rules on proposed updates so that I can catch violations before they affect users."
Description

Evaluate proposed role/policy modifications against configured Change Gate rules (e.g., approval requirements, segregation-of-duties checks, sensitive-scope restrictions) without applying changes. Show pass/fail outcomes per gate with detailed explanations, impacted user counts, and required approvers. Produce a what-if diff of authorization outcomes and a simulated audit record of the change. Expose a CI-friendly API/webhook to run dry-runs from policy-as-code pipelines and return machine-readable results for automated checks.

Acceptance Criteria
Conflict Detection and Resolution Suggestions
"As an IT admin, I want the system to flag policy conflicts and propose fixes so that I can prevent lockouts and privilege escalations."
Description

Automatically detect policy conflicts such as explicit denies overriding grants, overlapping role assignments, inheritance loops, and shadowed conditions that lead to unexpected escalations or lockouts. Rank conflicts by risk (e.g., potential admin lockout, privileged data exposure) and show the exact actions and resources affected. Provide guided fixes and auto-generated suggestions (e.g., reorder evaluation, add condition, split role), with a preview of their effect in the simulator before applying changes.

Acceptance Criteria
Least-Privilege Role Alternatives
"As a product ops lead, I want least-privilege role suggestions so that I can grant just enough access for the job without exposing unnecessary data."
Description

Given a set of target tasks (e.g., "triage themes," "export insights," "sync tickets"), compute and suggest least-privilege role or policy alternatives that enable only the necessary actions. Leverage historical usage patterns to avoid over-granting and present side-by-side diffs versus the current role. Provide risk annotations (e.g., sensitive data scopes granted) and enable one-click creation of a change request ticket in Jira/Linear with the proposed policy attached.

Acceptance Criteria
Data Exposure Preview with Redaction
"As a compliance officer, I want to preview data exposure with redactions so that I can assess privacy risk before approving a policy change."
Description

Preview the data a user would be able to access under current or proposed policies, including counts by data class (PII, financial, customer identifiers) and examples with field-level redaction. Allow drill-down by source (reviews, emails, tickets, chats), customer segment, and time window. Compute exposure deltas between current and proposed changes, highlight newly exposed sensitive fields, and block downloads/exports during simulation unless explicitly permitted by policy. Ensure previews are performant via sampled queries and enforce redaction policies consistently across UI and API.

Acceptance Criteria
Simulation Audit Trail and Access Controls
"As a security administrator, I want strict controls and auditing for simulations so that we meet compliance requirements and deter misuse."
Description

Enforce who may simulate whom (e.g., only admins; dual control for high-privilege identities) and record a detailed, tamper-evident audit trail for every simulation: initiator, target identity, policy set, time bounds, viewed resources, and configuration used. Provide retention controls, export to SIEM via webhook, and alerting for high-risk simulations (e.g., impersonating exec or enabling sensitive scopes). Require explicit acknowledgement in the UI, display a prominent banner during sessions, and auto-timeout stale simulations. Integrate with SSO and existing EchoLens RBAC.

Acceptance Criteria

Product Ideas

Innovative concepts that could enhance this product's value proposition.

Spike Siren Alerts

Real-time anomaly detection on theme spikes with per-segment baselines; pings Slack with impact and suggested owner. Cuts response time and catches rolling outages early.

Idea

Confidence Ledger

Tamper-evident audit trail for cluster changes and confidence scores, with reason codes and authorship. Restores trust and speeds debates with rollbacks and side-by-side diffs.

Idea

Close-Loop Courier

Auto-draft personalized replies using theme, customer history, and fix ETA; sends via Zendesk/Intercom and tracks acknowledgment. Reduces churn by closing feedback loops fast.

Idea

Segment-Aware Scoring

Weight rankings by revenue, plan, and lifecycle stage; simulate impact scenarios before pushing to Jira/Linear. Surfaces fixes that protect renewals, not just volume.

Idea

Zero-Click Source Sync

OAuth-driven setup that auto-discovers Zendesk, Intercom, Jira/Linear, and app-store accounts; pre-maps fields and webhooks. Cuts onboarding to minutes and prevents sync gaps.

Idea

Theme Health Score

Continuously scores each theme on severity, velocity, and customer value; shows a decaying heatbar and decay half-life. Guides when to act, watch, or archive.

Idea

Guardrail Roles

Opinionated roles and least-privilege permissions, including source access, theme merge rights, and sync controls. Prevents accidental cluster drifts and protects sensitive feedback.

Idea

Press Coverage

Imagined press coverage for this groundbreaking product concept.

Want More Amazing Product Ideas?

Subscribe to receive a fresh, AI-generated product idea in your inbox every day. It's completely free, and you might just discover your next big thing!

Product team collaborating

Transform ideas into products

Full.CX effortlessly brings product visions to life.

This product was entirely generated using our AI and advanced algorithms. When you upgrade, you'll gain access to detailed product requirements, user personas, and feature specifications just like what you see below.