Meeting scheduling

TimeTether

End Timezone Burnout

TimeTether is a scheduling assistant that finds conflict-free, recurring meeting times for remote-first product and engineering leads, automatically analyzing timezones and work windows, applying a fairness-driven rotation, and sending one-click invites. It halves after-hours meetings, cuts scheduling time, and ensures equitable meeting burdens across distributed teams.

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

TimeTether

Product Details

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

Vision & Mission

Vision
Unleash global collaboration for remote first teams by eliminating timezone friction with fair, conflict free meetings that respect everyone's time.
Long Term Goal
Within 4 years, TimeTether will eliminate 70% of after-hours meetings for 20,000 teams, protecting 200,000 employees and establishing scheduling equity as the industry standard.
Impact
TimeTether reduces scheduling time by 60% for remote-first product and engineering leads, halves after-hours meetings for affected teammates, saves managers up to two hours weekly, and increases perceived scheduling fairness by 40%, improving global meeting attendance.

Problem & Solution

Problem Statement
Remote-first product and engineering leads struggle to schedule recurring cross-timezone meetings without overburdening certain team members, because calendars and schedulers ignore work-hour sensitivity and lack fairness-driven rotation, forcing manual negotiation and late-hour meetings.
Solution Overview
TimeTether automatically analyzes team timezones and preferred work windows to suggest conflict-free recurring meeting slots, applies a fairness-driven rotation to evenly distribute inconvenient times, and sends one-click calendar invites to eliminate manual negotiation.

Details & Audience

Description
TimeTether is a scheduling assistant that removes timezone friction for globally distributed teams by automatically suggesting conflict-free meeting times. It targets remote-first product and engineering leads who need predictable, fair meetings across regions. TimeTether cuts scheduling time, halves after-hours meetings, and boosts attendance by recommending times that respect work hours and preferences. Its distinctive fairness-driven rotation algorithm spreads inconvenient slots equitably so no one team repeatedly sacrifices personal time.
Target Audience
Remote-first product and engineering leads (25–45) seeking fair, low-friction global scheduling, prioritizing team equity.
Inspiration
During a frantic product launch I scheduled a 3 a.m. meeting so one APAC engineer could join; afterward she admitted she'd slept through most of it. Seeing our good intentions turn into burnout, I sketched a fairness-driven algorithm on a whiteboard that honors work windows, suggests conflict-free recurring slots, and rotates unpopular times so no one repeatedly sacrifices nights or weekends.

User Personas

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

R

Release Ringmaster Rae

- Age 34–44; release/program manager at SaaS scale-up (200–1000 employees). - Remote-first; based in GMT±1; coordinates Americas, EMEA, and APAC squads. - STEM or CS degree; PMP/SAFe-savvy; 8–12 years delivery experience. - Owns release calendar, freeze windows, compliance gates, cross-team dependencies.

Background

Cut their teeth running late-night war rooms in a regional startup, burning weekends around hard deadlines. After joining a global scale-up, vowed to end after-hours chaos by standardizing cadences and guardrails.

Needs & Pain Points

Needs

1. Auto-rotate recurring release rituals across regions fairly. 2. Respect freeze windows and team work hours. 3. One-click updates when dependencies shift.

Pain Points

1. Recurring cutovers default to one region’s evenings. 2. Manual timezone math causes drift and confusion. 3. Reschedules cascade across squads, breaking commitments.

Psychographics

- Obsessed with predictability and low-variance delivery. - Believes fairness drives sustainable velocity. - Data-first; trusts metrics over anecdote. - Minimizes meetings that erode focus time.

Channels

1. Slack – work DMs 2. Google Calendar – primary 3. Jira – release boards 4. Email – updates 5. LinkedIn – peer groups

I

Incident Insight Ivan

- Age 28–40; SRE/Platform lead at cloud SaaS (100–800 employees). - Remote-first; lives in EST; collaborates with EMEA/APAC responders. - BS CS/EE; on-call rotation owner; 5–10 years reliability experience. - Accountable for incident reviews, SLAs/SLOs, and PagerDuty health.

Background

Started as a nocturnal firefighter, sleeping with PagerDuty by the bed. After repeated burnout in one region, implemented formal postmortems and equitable rotations to stabilize the team.

Needs & Pain Points

Needs

1. Rotate postmortems across timezones automatically. 2. Respect on-call shifts and quiet hours. 3. Batch recurring handoffs with one-click invites.

Pain Points

1. Same engineers attend reviews at 11 p.m. 2. Handoffs slip due to conflicting standups. 3. Manual reschedules break audit trails.

Psychographics

- Prevents toil; prizes humane on-call culture. - Blunt, pragmatic, allergic to calendar chaos. - Chooses tools that automate the boring. - Measures outcomes: MTTR, after-hours rates.

Channels

1. Slack – incident channels 2. PagerDuty – schedules 3. Google Calendar – primary 4. Email – incident follow-ups 5. LinkedIn – SRE communities

P

Partner Sync Priya

- Age 30–42; technical partner/alliances manager at API-first platform. - Remote; based in Singapore; spans APAC, EMEA, Americas counterparts. - 6–10 years in solutions/alliances; comfortable with Jira/Slack/HubSpot. - Owns partner roadmap cadences, integration checkpoints, and escalation bridges.

Background

Former solutions engineer turned alliance builder after witnessing integrations stall over logistics. Learned that equitable, reliable cadence keeps roadmaps moving and relationships warm.

Needs & Pain Points

Needs

1. Shareable, one-click partner-friendly scheduling links. 2. Fair rotation between partner and internal work windows. 3. Clear visibility into recurring commitments.

Pain Points

1. Partners feel forced into graveyard slots. 2. Email ping-pong derails meeting setup. 3. Conflicting internal ceremonies steal partner time.

Psychographics

- Relationship-first, but process-rigorous. - Values neutrality and perceived fairness. - Avoids friction; champions single-source-of-truth. - Time-respectful communicator, hates back-and-forth.

Channels

1. Email – primary external 2. Slack Connect – partner 3. Google Calendar – invites 4. LinkedIn – partner updates 5. Zoom – meetings

E

Enablement Educator Eli

- Age 29–41; engineering enablement/DevEx lead at mid-size tech company. - Remote-first; based in Berlin; audience spans NA, EMEA, APAC. - Ex-SWE; 6–9 years; owns onboarding and tooling adoption KPIs. - Coordinates with platform, security, and documentation teams.

Background

After launching internal tools that nobody used, learned timing kills adoption. Built a habit of rotating office hours and publishing predictable cadences.

Needs & Pain Points

Needs

1. Auto-rotate office hours across regions monthly. 2. Avoid collisions with sprint ceremonies. 3. Attendance insights by region/time.

Pain Points

1. Same region always gets the red-eye slot. 2. Competes with standups and planning. 3. Poor attendance without predictable cadence.

Psychographics

- Evangelizes craftsmanship and continuous learning. - Pragmatic about change management. - Champions inclusion through time equity. - Data-curious; tracks attendance trends.

Channels

1. Slack – announcements 2. Google Calendar – series 3. Confluence – schedules 4. Email – reminders 5. Zoom – webinars

R

Research Rhythm Rosa

- Age 27–38; Research Ops at product-led company (100–500 employees). - Hybrid/remote; based in Toronto; teams distributed globally. - Background in UX research; 5–8 years ops specialization. - Owns research cadence, consent/compliance logistics, and stakeholder attendance.

Background

Scaled from lone researcher to ops lead after missed learnings hurt launches. Instituted regular readouts and debriefs, but scheduling across squads became the bottleneck.

Needs & Pain Points

Needs

1. Batch debriefs that avoid focus blocks. 2. Equitable rotation for global stakeholders. 3. Reliable attendance tracking hooks.

Pain Points

1. Readouts collide with sprint planning and reviews. 2. APAC consistently joins outside work hours. 3. Manual nudges drain credibility.

Psychographics

- Empathy-driven, outcome-obsessed. - Protects maker time fiercely. - Values ritualized learning loops. - Prefers automation over nagging.

Channels

1. Slack – #research channels 2. Google Calendar – invites 3. Confluence – readouts 4. Email – summaries 5. Zoom – recordings

M

Metrics Maestro Maya

- Age 31–43; head of product analytics at venture-backed SaaS. - Remote; Denver-based; interfaces with execs, PMs, finance, and GTM teams. - MS in Statistics/Econometrics; 7–11 years analytics leadership. - Accountable for KPI cadence, quarter-end readouts, and experiment councils.

Background

Learned in consulting that bad timing kills stakeholder engagement. Now designs predictable, fair review rhythms that survive quarter-end chaos.

Needs & Pain Points

Needs

1. Schedule reviews avoiding month- and quarter-end crunch. 2. Rotate exec-friendly times across regions. 3. Quick rescheduling when forecasts shift.

Pain Points

1. Quarter-end collides with KPI cadences. 2. Exec calendars derail recurring rhythms. 3. After-hours reviews demotivate contributors.

Psychographics

- Evidence zealot; hates anecdotal debates. - Protects quarter-end focus time. - Demands operational elegance and clarity. - Skeptical until metrics prove value.

Channels

1. Slack – leadership channels 2. Google Calendar – overlays 3. Email – executive comms 4. Notion – dashboards 5. LinkedIn – analytics forums

Product Features

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

Smart Allocation

Auto-allocates monthly after-hours credits per team based on team size, time zone spread, and historical demand. Sets fair baselines from day one, reducing manual setup and preventing over- or under-budgeting so teams start balanced.

Requirements

Baseline Inputs Ingestion
"As a workspace admin, I want TimeTether to automatically pull team size, time zones, and historical meeting patterns so that Smart Allocation can set fair, data-driven after-hours baselines without manual setup."
Description

Automatically ingest team roster, member time zones, defined work windows, and historical meeting data to initialize Smart Allocation baselines. Supports Google Workspace and Microsoft 365 calendar connectors, normalizes time zones (including DST and regional holidays), applies sensible defaults when data is missing, and deduplicates overlapping events. Implements privacy-by-default (scopes limited to metadata needed for allocation) and clear consent prompts. Produces a clean, validated data model that enables accurate, low-touch baseline credit allocation from day one.

Acceptance Criteria
Calendar Connectors: Consent and Scoped Access (Google Workspace & Microsoft 365)
Given an org admin initiates a Google Workspace or Microsoft 365 connection When the OAuth consent screen is displayed Then only read-only scopes for event metadata (start/end time, start/end timezone, organizer id, attendee count, recurrence ids) and directory roster (name, email, primary timezone) are requested And event subject, description, and location scopes are not requested nor stored And the consent screen clearly states purpose: “Initialize Smart Allocation baselines” When consent is granted Then an audit log entry records connector type, admin id, granted scopes, and timestamp When consent is revoked by the admin Then ingestion stops within 60 seconds and all refresh/access tokens are deleted And subsequent scheduled ingestions fail closed with a clear “consent revoked” error
Roster, Time Zone, and Work Window Ingestion
Given a connected tenant and selected org unit/group scope When baseline ingestion runs Then all active users within scope are imported with fields: user_id, email, display_name, primary_calendar_timezone, work_window And suspended/deactivated users are excluded When org-level work hours policy exists Then member work windows inherit from policy unless overridden by user-level settings Then 100% of imported members have a resolved timezone and work window after applying defaults When duplicate roster records are encountered (same email or directory id) Then duplicates are merged deterministically using stable identifiers and latest-updated source
Historical Meetings Import and Deduplication
Given the default lookback window of 90 days is configured When event ingestion runs Then events within the window are imported per member with fields: start_utc, end_utc, source_timezone, iCalUID/UID, series_id, organizer_id And events outside the window are not imported When the same meeting appears on multiple calendars Then duplicates are collapsed using (iCalUID OR UID) OR (series_id AND overlapping start/end within 2 minutes) And no two events remain with identical member_id, start_utc, end_utc Then false-positive deduplication rate is ≤ 0.5% on the labeled test set When recurring series are present Then occurrences are expanded and linked to their series_id
Timezone Normalization, DST, and Regional Holidays
Given events span multiple timezones and DST transitions When normalization executes Then each event gains member_local_start and member_local_end computed from source_timezone with correct DST offsets And after-hours classification uses member_local_* times When a member’s region has a public holiday on the event date Then the day is flagged holiday=true per that region’s calendar When an event timezone differs from the member’s primary timezone Then both are preserved in the model and normalization is based on the event’s timezone
Missing Data Defaults and Transparent Fallbacks
Given a member lacks an explicit timezone When ingestion runs Then timezone is inferred from primary calendar settings; if unavailable, default to org timezone Given a member lacks defined work hours When ingestion runs Then default to 09:00–17:00 Monday–Friday in member-local time Given no historical meetings are available for a member in the lookback window When baselines are initialized Then historical after-hours load is set to 0 and confidence=low Then all applied defaults are captured per member in a downloadable validation report
Validated Data Model Output and Quality Gates
Given ingestion completes When the baseline data model is produced Then it contains entities and fields: Team, Member(timezone, work_window), EventNormalized(start_utc, end_utc, member_local_start/end, after_hours_flag, holiday_flag, source_connector, dedup_group_id) And schema validation passes with 0 critical errors and ≤ 1% non-critical warnings And PII exclusion is enforced: no event subject, description, or location persisted Then for teams up to 200 members with a 90-day lookback, end-to-end ingestion completes in ≤ 15 minutes at the 95th percentile When any critical quality gate fails Then Smart Allocation initialization is blocked and an actionable error report is surfaced to the admin
Fairness Allocation Algorithm v1
"As a team lead, I want credits allocated based on how distributed and active my team is so that after-hours burdens are shared proportionally from the start."
Description

Compute monthly after-hours credit baselines per team using a weighted model that factors team size, time zone spread, and historical after-hours demand. Applies guardrails (minimum/maximum credits per member), proportional scaling across teams, and cold-start defaults when history is sparse. Delivers deterministic, explainable outputs with per-team rationale and confidence scores. Includes a test harness with synthetic edge cases (e.g., extreme spreads, zero history) and versioned algorithm configuration for reproducibility.

Acceptance Criteria
Deterministic Weighted Baseline Computation
Given a fixed dataset of teams, time zones, and history and a specific algorithm configuration version When the allocation algorithm runs multiple times on different machines/environments Then all numeric outputs for each team match within an absolute tolerance of 1e-6 and all non-numeric fields match exactly And the output includes algorithm_version equal to the provided configuration version
Weighting Factors Influence Baseline as Configured
Given configuration weights for size, time zone spread, and historical demand and a fixture with known inputs When the algorithm computes raw baseline scores Then each team's raw score equals the normalized weighted sum of the three factors per configuration within an absolute tolerance of 1e-6 And when increasing only team size for a team, its raw score does not decrease and other teams' scores remain unchanged And when increasing only time zone spread or only historical demand for a team, its raw score does not decrease
Min/Max Per-Member Guardrails Enforcement
Given computed allocations and configured min_per_member and max_per_member guardrails When per-member credits are derived from team baselines Then every member's monthly credits are >= min_per_member and <= max_per_member And each team's total monthly credits equals the sum of its members' credits within an absolute tolerance of 1e-6 And when a team's raw per-member credits would violate guardrails, the output flags the applied guardrails and the count of affected members
Proportional Scaling Under Global Budget
Given a global monthly after-hours credit budget B and teams' raw baseline scores When scaling is applied Then the sum of final team credits equals B within an absolute tolerance of 1e-6 And if no team's guardrails are active, the ratio of final credits between any two teams equals the ratio of their raw scores within a relative tolerance of 1e-6 And if some teams hit guardrails, unaffected teams are rescaled to meet B and each team's reported output includes its deviation from the raw ratio
Cold-Start Defaults with Sparse or Zero History
Given a team whose history_coverage is below the configured cold_start_threshold When computing baselines Then the algorithm uses default priors instead of historical demand for that team And the team's confidence_score is <= the configured cold_start_confidence_max And the rationale states that cold-start defaults were applied and which factors were defaulted And given zero history across all teams, outputs are produced without error and reflect size and time zone spread factors per configuration
Explainable Outputs with Rationale and Confidence
Given the algorithm output per team Then it includes team_id, final_team_monthly_credits, per_member_credits_summary, raw_baseline_score, scaling_factor, applied_guardrails, factor_breakdown for size/spread/history, rationale_text, confidence_score in [0,1], algorithm_version, and generated_at timestamp (ISO 8601) And rationale_text references the three factors, any guardrails applied, and any scaling due to the global budget in human-readable sentences And given fixed inputs and configuration, rationale_text and confidence_score are identical across runs
Versioned Config and Synthetic Edge-Case Test Harness
Given algorithm_config_version=v1.X and a suite of synthetic cases (extreme time zone spread >= 12h, zero history, single-member team, very large team >= 200 members, identical teams tie case, missing/partial data) When the test harness executes Then all cases complete without exceptions, numeric outputs are non-negative and respect guardrails, and missing/partial data are handled per config defaults and flagged in rationale And outputs for these cases match stored golden snapshots for version v1.X within tolerances; if not, the test fails with an instruction to bump the version
Credit Ledger & Carryover Rules
"As an admin, I want a transparent ledger of after-hours credits with configurable carryover so that I can track usage accurately and avoid budget drift."
Description

Maintain a monthly credit ledger per team with transactions for issuance, consumption, manual adjustments, and expirations. Supports configurable carryover rules (cap by percentage or absolute value), end-of-month close-out, and idempotent operations to prevent double counting. Provides an auditable history with timestamps, actors, and reasons, plus an API for exporting ledger entries. Ensures data consistency with allocation updates and scheduler events.

Acceptance Criteria
Monthly Issuance Ledger Entry Creation
Given team T has a monthly allocation amount A for period P and no issuance logged for team T in period P When the issuance job runs at or after the start of period P Then exactly one transaction of type "issuance" is created for team T with amount = A and period = P And the transaction includes: transaction_id, team_id, period, type, amount, timestamp (ISO 8601), actor_type=system, actor_id=allocation_service, reason="monthly_issuance", balance_after And the team's ledger balance increases by A And the transaction appears in the team's auditable history within 1 minute of job completion
Consumption Recording From Scheduler Event
Given the scheduler emits a confirmed after-hours meeting event for team T with event_id E, cost X credits, and period P When the ledger service processes event E Then exactly one transaction of type "consumption" is created with amount = -X, period = P, external_reference_id = E, actor_type=system, actor_id=scheduler_service, reason="meeting_consumption" And the team's ledger balance decreases by X And if a cancellation event referencing E arrives before the meeting starts, then a single "reversal" transaction is created with amount = +X and reason="meeting_cancellation_reversal" that references the original consumption transaction_id
Manual Adjustment With Audit Trail
Given an authorized admin U calls the adjustments API for team T to change balance by amount M with reason R (non-empty) When the request is validated and accepted Then a transaction of type "manual_adjustment" is created with amount = M, actor_type=user, actor_id=U, reason=R, timestamp (ISO 8601), period = the current accounting period for team T And the team's ledger balance changes by M And the transaction is immutable; any change must be a compensating "manual_adjustment" referencing the original transaction_id
Carryover Cap by Percentage at Close-out
Given team T has carryover.type = "percentage" and carryover.cap_percent = p for period P, with remaining_credits_end_of_P = R and monthly_issuance_for_P = A When the close-out job runs at the configured close-out time for period P Then carryover_amount = min(R, floor((p/100) * A)) And a single transaction of type "carryover" is created in period P+1 with amount = carryover_amount and reason = "percentage_carryover" And if R > carryover_amount, a single transaction of type "expiration" is created in period P with amount = -(R - carryover_amount) and reason = "carryover_expiration" And balances for periods P and P+1 reflect these transactions
Carryover Cap by Absolute Value at Close-out
Given team T has carryover.type = "absolute" and carryover.cap_absolute = N for period P, with remaining_credits_end_of_P = R When the close-out job runs at the configured close-out time for period P Then carryover_amount = min(R, N) And a single transaction of type "carryover" is created in period P+1 with amount = carryover_amount and reason = "absolute_carryover" And if R > carryover_amount, a single transaction of type "expiration" is created in period P with amount = -(R - carryover_amount) and reason = "carryover_expiration" And balances for periods P and P+1 reflect these transactions
Idempotent Operation Handling Across Jobs and Events
Given an operation O with idempotency_key K derived from (team_id, type, period, external_reference_id if present) When O is retried one or more times with the same K Then no additional transaction is created and the original transaction_id is returned And the ledger balance remains unchanged after the first successful application And duplicated scheduler events with the same external_reference_id produce only one "consumption" transaction And rerunning the issuance or close-out jobs for the same team-period does not create duplicate "issuance", "carryover", or "expiration" transactions
Ledger Export API Returns Auditable History
Given a caller with export scope requests GET /api/v1/ledger/entries with team_id = T, date range [S, E], optional types filter, and pagination params When the request is valid Then the API responds 200 within 2 seconds with a paginated JSON list ordered by timestamp ascending then transaction_id And each entry includes: transaction_id, team_id, period, type, amount, balance_after, timestamp (ISO 8601), actor_type, actor_id, reason, external_reference_id (nullable) And the response includes pagination cursors (next, prev) when applicable And the returned entries match the filters and page size limit
Admin Controls & Policy Overrides
"As an org admin, I want to adjust allocation policies and set exceptions so that Smart Allocation aligns with our internal rules and edge cases."
Description

Offer an admin console and API to override team baselines, set org-wide caps, lock policies, exclude specific roles or users, and tune fairness weights (e.g., time zone spread vs. demand). Includes role-based access control, validation with safe ranges, change history with diffs, and a sandbox preview to show the impact of changes before applying them. Aligns Smart Allocation with organizational policies and exceptions without undermining fairness.

Acceptance Criteria
Override Team Baseline Allocation (Console)
- Given an Org Admin with Policy Admin permission is authenticated with MFA in the admin console, When they open a team’s Smart Allocation settings, Then the baseline credits field shows the current value and displays the safe range (min/max). - When the admin enters a value within the safe range and provides a required change reason (min 10 characters), Then the Preview action becomes enabled and shows a sandbox forecast for the next 3 months including per-user after-hours counts, team total credits, and fairness index delta. - When Apply is clicked and confirmed, Then the override is persisted, a recalculation job is enqueued within 15 seconds, and both the console and GET /api/v1/teams/{teamId}/allocation reflect the new baseline within 60 seconds. - Then an audit log entry is created capturing actor, timestamp (UTC), reason, before/after values, and a diff ID; and a success confirmation is displayed to the admin. - When an entered value is outside the safe range, Then Preview and Apply are disabled and a validation message with code ALLOC_RANGE_VIOLATION is shown.
Set Org-Wide After-Hours Cap and Policy Lock
- Given an Org Admin sets an org-wide monthly after-hours cap within the safe range, When they toggle Lock Policies = On, Then the cap becomes read-only for team admins in both console and API. - When a team-level baseline or cap change would exceed the org-wide cap, Then the console blocks save with inline error and the API returns 422 with code CAP_LOCK_ENFORCED. - Then enforcement is consistent across all endpoints (UI and API), and attempts to bypass via bulk import or hidden routes are rejected with 403. - When the org-wide cap is reduced below current aggregate team baselines, Then the next allocation cycle redistributes to comply and a warning notification is sent to Org Admins within 5 minutes. - Then change is versioned with audit entry including before/after cap and lock state.
Tune Fairness Weights With Safe Ranges
- Given fairness weights for timezone_spread and demand are editable sliders with allowed range [0.0, 1.0], When the sum is not equal to 1.0, Then the UI shows a validation error and Apply is disabled; the API returns 422 with code WEIGHT_SUM_INVALID. - When both weights are within range and sum to 1.0, Then Preview computes sandbox projections showing fairness index (0–100), predicted after-hours distribution, and delta vs current settings. - When Apply is confirmed with a change reason, Then new weights are persisted, propagated to the allocation engine within 60 seconds, and reflected by GET /api/v1/policies/fairness. - Then an audit log entry records before/after weights, actor, timestamp, and diff.
Exclude Specific Roles and Users From Allocation
- Given an Org Admin selects one or more roles and individual users to exclude from after-hours allocation, When exclusions are saved in Preview, Then the sandbox shows those identities with excluded=true and projected 0 after-hours assignments for the next allocation cycle. - When exclusions would make allocation infeasible (no eligible members), Then Apply is disabled and the UI displays error code EXCLUSION_INFEASIBLE; API returns 409 with the same code and a list of conflicting teams. - When exclusions are applied, Then GET /api/v1/teams/{teamId}/members includes excluded flags, and the allocation engine produces 0 after-hours assignments for excluded identities in the next cycle. - Then an audit entry captures the added/removed exclusions by user/role with before/after diff.
Change History, Diffs, and Revert
- Given any policy change (baseline override, cap/lock, fairness weights, exclusions), Then an immutable audit record is created with actor, timestamp (UTC), resource, change reason (required), and structured before/after JSON diff. - When a user views History for a team or org, Then entries are listed in reverse chronological order with filters (date range, actor, policy type) and can be exported as CSV. - When Revert is invoked on a prior entry, Then the system creates a new change that restores the previous values, links to the original entry, and triggers a recalculation within 60 seconds. - Then API GET /api/v1/audit supports pagination, diff retrieval by diffId, and returns 200 with results matching the console view.
Sandbox Preview Impact Analysis
- Given pending policy edits, When the admin clicks Preview, Then the system runs a sandbox simulation using the last 90 days of demand and current timezone data to forecast the next 8 weeks. - Then the preview displays per-team credit usage, per-user projected after-hours counts, and fairness index delta vs current policies, and provides a downloadable CSV report. - Then the simulation completes in under 10 seconds for orgs up to 2,000 users; no production data or schedules are mutated; and a shareable preview link expires after 72 hours. - When Apply is not confirmed, Then no policy values change and no recalculation job is enqueued.
RBAC and API Authorization for Overrides
- Given role-based access control, When a user without Policy Admin permission accesses policy override screens or endpoints, Then the UI hides edit controls and the API returns 403 (policy:write required) for mutating calls. - When a user with Policy Admin permission and valid OAuth2 token (scope policy:write) sends a PATCH with Idempotency-Key, Then the change is applied once and repeated requests within 24 hours return 200 with the original result. - Then all policy mutations require active MFA on the session; otherwise the console prompts for MFA and the API returns 401 MFA_REQUIRED. - Then write endpoints are rate limited to 100 requests/min/org; excess returns 429 with Retry-After header.
Scheduling Consumption Hook
"As a meeting organizer, I want credits to be automatically applied and tracked when I schedule after-hours meetings so that our team stays within policy without manual effort."
Description

Integrate Smart Allocation with the scheduling flow to pre-check available credits and decrement them when after-hours meetings are booked. Supports soft limits (warnings) and hard caps (blocking) per team, handles multi-team meetings by splitting consumption per attendance share, and rolls back credits on cancellation or reschedule. Ensures real-time consistency and aligns with fairness rotation so that credits reflect actual after-hours burden.

Acceptance Criteria
Pre-Check Available After-Hours Credits During Scheduling
Given team monthly after-hours credits are configured with soft limit and hard cap thresholds and credit unit is minutes When a scheduler selects a proposed timeslot for a meeting with identified attendees and teams Then the system determines which attendees are outside their work windows at that time using each attendee’s timezone and work window configuration And the system calculates predicted per-team deductions using the split rule without modifying any balances And the system displays remaining credits and predicted deductions per team before confirmation And if any team’s predicted remaining credits would be at or below its soft limit, a non-blocking warning is shown identifying the team(s) And if any team’s predicted remaining credits would exceed its hard cap, the booking action is blocked pending changes
Decrement Credits on After-Hours Booking Commit
Given a meeting is confirmed with an idempotency key composed of meetingId and occurrenceId When the meeting qualifies as after-hours for one or more attendees Then the system atomically decrements team credits according to the split rule based on attendees outside their work windows And only teams with at least one attendee outside their work window are charged And ledger entries record meetingId, occurrenceId, teamId, minutes debited, pre-balance, post-balance, timestamp, and idempotency key And repeated commits with the same idempotency key do not change balances And if no attendees are outside their work windows, no credits are decremented
Soft Limit Warning Without Blocking
Given a team has a configured soft limit threshold above 0 remaining credits When a booking’s computed deduction would bring the team’s remaining credits to or below the soft limit but not exceed the hard cap Then the system allows the booking to proceed And the system surfaces a visible warning identifying the team and projected remaining credits And the debit ledger entry is tagged with soft-limit-breached=true for audit and reporting
Hard Cap Enforcement Blocks Booking
Given a team has a configured hard cap When a booking’s computed deduction would cause the team’s remaining credits to drop below 0 relative to the cap for the current month Then the system blocks the booking before commit And the user is shown a specific error listing the blocking team(s), required minutes, and remaining minutes And no debit ledger entries are created
Split Credit Consumption for Multi-Team Meetings
Given a meeting includes attendees mapped to one or more teams When computing per-team deductions for an after-hours meeting Then total chargeable minutes equal the meeting duration in minutes And each team’s share equals (count of that team’s attendees outside work windows) divided by (total attendees outside work windows across all teams) And each team’s minutes debited equal round-half-up(share × total chargeable minutes) And rounding ensures the sum of all teams’ minutes equals the total chargeable minutes by assigning any remainder to the team(s) with largest fractional parts; break ties by ascending teamId And if the total attendees outside work windows equals 0, no team is charged
Rollback Credits on Cancellation and Reschedule
Given a booked meeting occurrence has an associated debit ledger entry or entries When the occurrence is canceled before its scheduled start time Then the system creates reversal entries that credit back exactly the minutes previously debited per team and marks them as reversals linked to the original debit And team balances reflect the reversal immediately When an occurrence is rescheduled Then the system recalculates per-team deductions for the new time using the split rule and applies only the delta between the original debit and the new computation as atomic adjustments And if the rescheduled time results in no after-hours burden, all prior debited minutes are reversed And adjustments are idempotent for repeated reschedule events for the same occurrenceId and new start time
Real-Time Consistency and Concurrency Control
Given multiple bookings that affect the same team are attempted concurrently When commits are processed Then per-team credit decrements are serialized to prevent overspending and never allow balances to go below 0 under a hard cap And pre-check values reflect the most recent committed state for the requesting user And any commit that races with a stale pre-check is validated against current balances and fails with a clear error if it would breach a hard cap And all debit, reversal, and adjustment operations are atomic and durable, emitting a single event per ledger change
Allocation Insights & Alerts
"As a team lead, I want proactive alerts and clear reports on our after-hours credits so that I can adjust scheduling before we run out or over-allocate."
Description

Provide dashboards and alerts showing per-team baselines, consumption, trends, and forecasts for month-end position. Configure thresholds (e.g., 50/80/100%) to trigger Slack/email notifications, highlight anomalies (sudden spikes, persistent underspend), and suggest actions (adjust baseline, change meeting times). Support CSV export and an embeddable widget for team spaces. Improves visibility and proactive management of after-hours budgets.

Acceptance Criteria
Per-team insights dashboard shows baseline, consumption, trends, and forecast
Given a team has a monthly baseline B hours and month-to-date consumption C hours with at least 30 days of historical data When a user opens the Allocation Insights dashboard for that team Then the dashboard displays baseline (B), MTD consumption (C), remaining (B−C), a 90-day trend chart, and a month-end forecast F with confidence range R And the displayed values match the latest processed dataset as of a visible "Last updated" timestamp And the forecast F and confidence R are shown in both hours and percent of baseline
Configurable consumption thresholds trigger alerts
Given thresholds of 50%, 80%, and 100% are configured for a team When the team’s consumption percentage crosses any configured threshold upward for the first time in the month Then an alert event is created containing team name, threshold crossed, current consumption %, consumed hours, remaining hours, and forecast status And the alert is delivered to all enabled channels within 5 minutes of the crossing And each threshold triggers at most once per month per team unless manually reset by an admin
Multi-channel alert delivery and deduplication
Given Slack and Email channels are configured for a team When an alert event is dispatched Then a Slack message posts to the configured channel with title, key metrics, and a deep link to the team’s dashboard And an email with equivalent content is delivered to all configured recipients And duplicate alerts for the same team-threshold within a 24-hour window are suppressed And delivery failures are logged and retried up to 3 times with exponential backoff; permanent failures are surfaced in an admin console
Anomaly detection for spikes and persistent underspend
Given daily after-hours consumption for the last 30 days is available When a day’s consumption exceeds the 30-day mean by ≥2 standard deviations or increases by ≥50% week-over-week Then the day is flagged as a "Spike" in the dashboard and recorded in the anomaly log with date and magnitude And an "Anomaly: Spike" alert is generated once per detected spike Given two consecutive months with <30% of baseline consumed When the second month passes 80% of its duration Then flag "Persistent underspend" for the team and record it in the anomaly log
Actionable recommendations for adjustments
Given an anomaly is flagged or the forecasted month-end position is >100% (overrun) or <70% (underspend) of baseline When the user opens the alert or anomaly details pane Then the system shows at least one recommendation (e.g., adjust baseline by ±D hours, shift meeting windows) with an estimated impact on month-end forecast and fairness rotation And the recommendation includes a one-click action that routes to the relevant settings screen with proposed values prefilled And applied or dismissed recommendations are logged with user, timestamp, and outcome
CSV export of allocation insights
Given a user with view permission selects a date range and one or more teams When the user clicks "Export CSV" Then a CSV is generated within 10 seconds and made available for download And the CSV contains one row per team per month with columns: team_id, team_name, month, baseline_hours, consumed_hours, remaining_hours, thresholds_crossed, anomaly_flags, forecast_hours, forecast_confidence, last_updated And aggregated totals in the CSV match the dashboard values for the same filters to within 0.1 hours
Embeddable widget for team spaces
Given a user generates an embeddable widget for a specific team with view-only scope When the embed code is placed into an external team space Then the widget renders baseline, MTD consumption, remaining, a trend sparkline, and month-end forecast consistent with the dashboard And the widget loads in under 3 seconds on a 3G connection and refreshes data at least every 15 minutes And access requires a valid share token; revoking the share invalidates widget access within 5 minutes
Scenario Simulator
"As a manager, I want to simulate team and time zone changes so that I can understand how our after-hours baseline would shift before making decisions."
Description

Enable what-if simulations that let admins and managers model changes (adding/removing members, shifting time zones, altering meeting frequency) and see the projected impact on monthly credit baselines and expected after-hours rate. Provides side-by-side comparisons with current state, sharable scenario links, and cached runs for repeatability. Helps leaders plan team changes and set expectations before they affect live allocations.

Acceptance Criteria
Run What-If Simulation With Team Changes
Given an admin or manager with Scenario Simulator access And a team with existing Smart Allocation baselines When the user creates a scenario that includes at least one change: add/remove members, shift one or more member time zones, or adjust meeting frequency And clicks Run Simulation Then the system computes and returns projected monthly credit baselines per team and the expected after-hours meeting rate And the scenario is assigned a unique scenario ID and timestamp And the inputs and outputs are persisted for later retrieval
Side-by-Side Comparison With Current State
Given a completed scenario When viewing results Then a side-by-side comparison view displays current vs scenario values for: monthly credit baseline, expected after-hours rate, and team totals And deltas (absolute and percentage) are shown for each metric with visual indicators of increase/decrease And totals reconcile within 0.1 credits of the sum of itemized rows due to rounding, otherwise an error banner is shown And the user can toggle to hide/show unchanged rows
Shareable Scenario Link With Access Controls
Given a completed scenario When the user generates a shareable link Then the link uses a non-guessable token with at least 128 bits of entropy And recipients with org access and view permission can open a read-only scenario view; others receive a 403 And the owner can set an expiration time or revoke the link; revocation takes effect within 60 seconds And opening the link renders the cached snapshot of results and inputs, independent of subsequent live data changes
Cached Scenario Re-run Consistency
Given a completed scenario stored in cache When the user re-runs the scenario without changing inputs Then the returned results match the cached snapshot exactly for the same allocation engine version And if the allocation engine version has changed since the snapshot, the UI indicates the version difference and offers Recompute; choosing Recompute creates a new snapshot while preserving the original And cached scenarios are retained and retrievable for at least 30 days unless explicitly deleted by the owner
Input Validation and Error Handling
Given the scenario builder form When the user enters invalid data (e.g., negative team size, unsupported time zone, meeting frequency outside 0–14 per week) Then inline validation messages identify the specific fields, and the Run Simulation action is disabled And submitting with invalid payloads via API returns 400 with field-level errors; no scenario is created And if a simulation job fails, the user sees a retriable error message with a correlation ID and no partial results are saved
Performance and Scale for Large Teams
Given a team of up to 500 members across up to 20 time zones When running a scenario that modifies members, time zones, and meeting frequency Then 95th percentile simulation completion time is under 8 seconds and 99th percentile under 15 seconds And the UI maintains responsiveness (>45 FPS) during result rendering for up to 2,000 row items And concurrent execution of up to 10 scenarios per org does not degrade p95 latency beyond 20%
Export and Download of Simulation Results
Given a completed scenario When the user exports results Then a CSV export includes: inputs, per-team projected monthly credits, expected after-hours rate, and deltas vs current And a PDF export includes the side-by-side comparison view and scenario metadata (ID, timestamp, engine version) And exported files are generated within 5 seconds and are accessible via a secure, expiring link for 7 days

Rollover Flex

Lets unused credits roll over with configurable caps and expiries to smooth launch spikes and quarter-end crunches. Avoids end-of-month scramble while keeping after-hours rates stable and predictable.

Requirements

Rollover Policy Engine
"As a workspace admin, I want to define clear rollover rules with caps and expiries so that unused credits carry forward predictably without creating runaway balances or billing surprises."
Description

Implements configurable rollover rules at the workspace and plan levels, allowing admins to set caps (percentage of monthly allocation or absolute credit count), expiry windows (e.g., N days/months), grace periods, minimum carryover thresholds, FIFO consumption order, rounding rules, and eligibility by credit type (standard vs. after-hours). Applies policies deterministically at billing cycle close with timezone awareness, supports effective-dated changes and policy versioning, and handles edge cases such as DST shifts, leap years, variable month lengths, and partial cycles. Ensures idempotent processing so re-runs produce identical outcomes and exposes internal metrics for monitoring rollover execution and exceptions.

Acceptance Criteria
Apply Caps and Minimum Threshold at Billing Close
Given a workspace with monthly allocation A=1000 credits and unused U=350 at cycle close, a percentage cap of 20%, an absolute cap of 150, a minimum carryover threshold of 25, and rounding rule "floor to whole credit" When the billing cycle closes in the workspace billing timezone Then the rolled amount equals floor(min(U, 0.20*A, 150)) = 150 and is >= minimum threshold, so 150 credits are added to the rollover bucket And the rollover ledger entry records policy_version, source_cycle, calculation_inputs, and reason_codes including CAP_PCT and CAP_ABS And the post-close credit balance increases by exactly 150 credits
Expiry Window and Grace Period Enforcement
Given rollover credits with expiry_window=45 days and grace_period=5 days from the rollover timestamp When the current time reaches expiry_window + grace_period in the workspace billing timezone Then all unconsumed credits from that rollover bucket are marked expired and become ineligible for consumption And an "expire" ledger entry is created with policy_version, bucket_id, expired_amount, and expiry_reason=WINDOW_ELAPSED And the available balance reflects the removal within 1 minute of the expiry boundary
FIFO Consumption and Credit Type Eligibility
Given a user consumes 60 standard credits and has eligible standard buckets B1(rolled, created earlier, 30), B2(rolled, created later, 40), and B3(monthly current, 50), and ineligible buckets of type after-hours exist When the consumption occurs Then credits are deducted FIFO by created_at across eligible standard buckets: 30 from B1, 30 from B2, leaving B2 with 10 and B3 untouched And after-hours buckets are never used for standard consumption And each deduction is logged with bucket_id, amount, and remaining_balance
Effective-Dated Policy Change and Versioning at Cycle Boundary
Given policy V1 active through 2025-10-31 23:59:59 workspace local and policy V2 effective 2025-11-01 00:00:00 When the October 2025 cycle closes Then rollover is computed under V1 and resulting credits are tagged policy_version=V1 When the November 2025 cycle closes Then rollover is computed under V2 and resulting credits are tagged policy_version=V2 And immutable audit logs link each rollover bucket to the policy_version used
Idempotent Rerun Produces Identical Outcomes
Given the cycle-close job for workspace W and cycle C completed successfully and produced rollover ledger entry E with deterministic run_key When the job is retried with the same inputs and run_key Then no new rollover or expiry entries are created, balances remain unchanged, and the job result is SKIPPED_IDEMPOTENT And metrics increment idempotent_skips_total by 1 for workspace W and cycle C
Timezone, DST, Leap Year, and Partial Cycle Handling
Given a workspace timezone America/Los_Angeles with a billing anchor on the 31st When closing the February 2028 and March 2028 cycles where a DST spring-forward transition occurs in March Then the engine executes a single close at 00:00 local time on the computed cycle-end date (last calendar day if the anchor day does not exist) and runs exactly once despite DST changes And cycle length calculations are calendar-aware (e.g., 29 days for Feb 2028) and partial cycles are prorated linearly by days-in-cycle And all ledger/audit timestamps are recorded in UTC with the workspace timezone context stored as metadata
Metrics and Exception Telemetry
Given a successful cycle close that rolls 120 credits and expires 10 When metrics are emitted Then credits_rolled_total=120, credits_expired_total=10, processing_latency_ms is recorded, and status=success with labels workspace_id, plan_id, policy_version, cycle_end And on any exception, an exception event is recorded with error_code, workspace_id, cycle_end, and stack_trace_id, and exceptions_total increments by 1 And if exceptions_total > 0 in any 5-minute window, an alert signal is produced
Credit Ledger & Expiry Processor
"As a finance operator, I want an auditable credit ledger and expiry processor so that I can reconcile balances, correct errors safely, and satisfy audit requirements."
Description

Creates an immutable, event-sourced ledger capturing credit allocations, usage, rollovers, expirations, adjustments, and corrections with timestamps, source, actor, and reason codes. Provides a scheduled and on-demand processor that computes expirations and carryovers, reconciles balances, and emits audit events. Supports compensating entries for admin corrections, backfills for historical policy changes, and full traceability for financial audits. Designed for scale with partitioning, pagination, and idempotent operations; includes reconciliation reports and integrity checks to detect drift between ledger and computed balances.

Acceptance Criteria
Immutable Event-Sourced Ledger Append
Given a tenant and a valid Allocation event payload with a unique idempotency key When the Append Ledger Event API is called Then a new append-only ledger record is created with type=Allocation, timestamp, source, actor, and reason code populated and non-null And no existing ledger records are updated or deleted And a subsequent attempt to update or delete any ledger record is rejected with 405 and no mutation occurs And a second append with the same idempotency key returns 200 with the original eventId and no duplicate record is created And an append with a reused clientEventId but different payload is rejected with 409 conflict
Scheduled Expiry and Rollover Processing
Given credits with defined expiry dates, rollover caps, and a tenant policy timezone When the scheduled processor runs at the policy-defined schedule Then credits past their expiry boundary are expired and ledgered as Expiration events And eligible balances are carried over up to the configured cap and ledgered as Rollover events And resulting computed balances match (Total Allocations + Rollovers − Usage − Expirations ± Adjustments) to within 0 difference And all processor actions emit audit events with runId, counts, and durations And re-running the same schedule window is idempotent and produces no additional net ledger changes
On-Demand Recompute and Idempotency
Given a tenant, a date range, and an on-demand recompute request When the processor is triggered manually for that range Then it produces the same Expiration and Rollover events as the scheduled run for the same inputs And the run returns a runId with metrics (items scanned, items mutated, duration) And concurrent executions for overlapping ranges do not double-apply mutations (idempotent by run window) And a retry of the same request with the same parameters results in zero additional mutations and a Pass status in the run summary
Admin Compensating Corrections
Given an erroneous Usage or Allocation event identified by eventId and a permitted admin user When the admin submits a compensating Adjustment referencing the original eventId and a required reason code Then a new Adjustment ledger record is appended that reverses or amends the prior impact without altering the original record And the post-adjustment computed balance equals the expected corrected balance And the adjustment is traceable via correlationId/originalEventId linkage in audit exports And unauthorized users receive 403 and no mutation occurs
Historical Policy Backfill
Given a policy change (e.g., rollover cap or expiry rule) with an effective-from date in the past When a backfill job is executed for the affected period and tenants Then only compensating ledger events needed to align historical balances are appended; original events remain immutable And the backfill is resumable and can safely be re-run without duplicating compensations (idempotent by job key) And the post-backfill reconciliation shows zero drift between computed balances and ledger-derived balances for the period And audit output includes policy version, effective-from date, and counts of events examined/adjusted
Reconciliation Report and Drift Detection
Given the ledger and computed balances for all tenants When the nightly reconciliation runs Then for each tenant and product, the report shows ledger sum, computed balance, and drift amount And any non-zero drift beyond 0 triggers an alert with tenant identifiers and runId And the report is paginated, exportable as CSV/JSON, and retains history for 90 days And integrity checks include detection of out-of-order event timestamps and missing reason codes, with failures logged and surfaced
Scale, Partitioning, and Pagination Performance
Given a system with >=100M ledger events across tenants When listing events via the API with pageSize=1000 Then responses are partition-aware (e.g., by tenantId and month), consistently ordered by eventAt,eventId, and return a nextPageToken And p95 list latency is <=300 ms per page under nominal load, and no request exceeds 60 s timeout under stress And ingestion and processing are horizontally scalable, sustaining >=5k events/sec per worker without data loss And memory usage of a single processor worker remains <=512 MB while processing large partitions
Admin Policy Configuration & Simulation UI
"As a workspace admin, I want to configure rollover policies and simulate their impact so that I can choose settings that smooth usage without unintended expirations or cost spikes."
Description

Delivers a role-based admin interface to create, edit, and schedule effective-dated rollover policies with per-workspace and per-plan overrides. Includes a simulation tool that previews the next 1–3 cycles, estimating carryovers, expirations, and projected balances under different caps and expiry settings. Provides validation, warnings when policies could cause large expirations or exceed caps, inline help/tooltips, and change history with diffs. Displays how policy changes affect billing and scheduling outcomes before publishing.

Acceptance Criteria
RBAC: Admin-Only Policy Management
Given a user with Admin role in Workspace W When they open the Rollover Policies UI Then they can create, edit, schedule, simulate, and publish policies for Workspace W Given a user with Viewer role in Workspace W When they open the Rollover Policies UI Then they can view policies and run simulations in read-only mode and cannot Save or Publish (controls disabled) Given a user without access to Workspace W When they navigate to the Rollover Policies URL Then they receive an Access Denied message and no policy data is exposed
Create and Schedule Effective-Dated Rollover Policy with Overrides
Given an Admin in Workspace W and no overlapping policy for the selected time window When they create a policy with cap >= 0 (integer), expiryDays >= 0 (integer), and effectiveStart >= current UTC timestamp Then the form validates inputs, prevents overlaps, and allows Save as Draft Given a Draft policy When the Admin schedules it with a future effectiveStart Then the policy status becomes Scheduled and it is set to supersede the current Active policy at effectiveStart without affecting current calculations Given a plan-level default policy P When the Admin adds a per-workspace override for Workspace W Then fields not explicitly changed inherit from P, and simulations/use for W reflect the override while other workspaces continue using P
Simulate 1–3 Upcoming Cycles with Caps and Expiry
Given existing credit balances and current policy inputs for a selected scope When the Admin selects numberOfCycles in {1,2,3} and runs Simulation Then for each upcoming cycle the UI displays startingBalance, earnedCredits, carryoverApplied, expirations, and projectedEndingBalance as non-negative integers Given the Admin changes cap or expiryDays and re-runs Simulation When results are recalculated Then the UI updates per-cycle metrics and shows delta versus the current policy for each metric Given the same inputs are used When the Simulation is run multiple times Then results are deterministic and return within 2 seconds for up to 500 scoped entities
Warnings for Large Expirations or Cap Exceedances
Given a Simulation projects expirations exceeding 20% of startingBalance in any upcoming cycle for the selected scope When the preview is displayed Then a warning banner appears listing impacted scopes and quantifying total credits expiring and the percentage Given projectedEndingBalance exceeds the configured cap in any upcoming cycle When the preview is displayed Then a warning indicates cap clipping and shows the number of credits that would be discarded Given validation errors exist (e.g., negative values, overlapping effective dates) When the Admin attempts to Publish Then Publish is blocked with inline error messages; warnings do not block Publish
Policy Change History with Field-Level Diffs
Given a policy is saved (Draft, Scheduled, or Published) When the save completes Then a history entry is recorded with actor, UTC timestamp, scope (plan/workspace), and field-level diffs for cap, expiryDays, effectiveStart, and overrides (before -> after) Given multiple versions exist When the Admin opens the History view Then entries are ordered newest-first and selecting an entry shows only the fields that changed with before/after values Given two versions are selected for comparison When Compare is clicked Then a side-by-side diff is shown highlighting changed fields only
Pre-Publish Impact Preview: Billing and Scheduling Outcomes
Given a policy change is in Draft or Scheduled state When the Admin opens Impact Preview Then the UI displays for the next billing cycle the estimated charge delta versus the current policy and for the next 1–3 scheduling cycles the expected change in after-hours meetings and projected balance distributions Given the same inputs drive Simulation and Impact Preview When values are compared Then Impact Preview metrics match Simulation outputs for the same cycles (within a rounding tolerance of 1 credit) Given the Admin clicks Publish When the confirmation modal appears Then it summarizes key impacts and requires explicit confirmation; after confirmation, the policy becomes Active at effectiveStart and does not affect calculations before that time
Inline Help and Tooltips Coverage
Given the Rollover Policy form is displayed When the Admin focuses or hovers on cap, expiryDays, effectiveStart, override scope, or inheritance fields Then a tooltip appears with a plain-language description and example without obscuring the input and closes on Esc or blur Given keyboard-only navigation When tabbing through help icons Then tooltips are accessible with ARIA labels and readable by screen readers Given a Learn More link in help content When clicked Then the corresponding documentation opens in a new browser tab
Proactive Expiry Notifications & Integrations
"As a workspace admin, I want proactive alerts about credits nearing expiry so that I can take action to use them or adjust policies before value is lost."
Description

Sends configurable alerts to admins and team leads when credits approach expiry (e.g., 14/7/3 days), with in-app, email, and Slack channels and a consolidated month-end digest. Includes quiet hours, localization, and per-recipient preferences. Provides deep links to schedule backlog or adjust policies (permissions permitting) and emits webhooks for upcoming-expiration events to integrate with BI/automation tools. Ensures rate limiting and deduplication to avoid alert fatigue and logs delivery outcomes for compliance reporting.

Acceptance Criteria
Threshold-Based Multi-Channel Alerts With Preferences and Rate Limiting
Given org-level alert thresholds are set to 14/7/3 days before credit expiry and a recipient (admin or team lead) has in-app, email, and Slack enabled, when a credit bundle crosses a threshold, then the recipient receives exactly one notification per enabled channel for that threshold within 10 minutes. Given a recipient has per-recipient preferences that modify thresholds or disable channels, when alerts are evaluated, then the recipient’s preferences override org defaults for that recipient. Given multiple credit bundles for the same recipient cross the same threshold on the same day, when alerts are sent, then a single batched message per channel lists all affected bundles. Given an alert was already sent for a bundle/threshold/channel, when retries or duplicate jobs occur, then deduplication prevents additional user-visible messages for 24 hours for that same bundle+threshold+channel. Given rate limiting is set to a maximum of 3 notifications per recipient per rolling hour per channel, when more notifications are scheduled, then excess alerts are deferred to the next window and marked as deferred in logs.
Timezone-Aware Quiet Hours Deferral
Given a recipient’s quiet hours are 20:00–08:00 in their configured timezone, when an alert would be sent during quiet hours, then email and Slack are deferred until 08:00 local time and in-app indicators update without push. Given quiet hours span a weekend and the recipient enabled “skip weekends,” when the defer time lands on Saturday or Sunday, then delivery occurs at 08:00 on the next business day. Given a recipient updates their timezone or quiet-hours window, when the next evaluation runs, then subsequent deliveries honor the updated settings.
Localized Notifications and Preferences
Given a recipient locale is set to a supported language (e.g., en-US, fr-FR, ja-JP), when a notification is rendered, then subject/body text, dates/times, and pluralization are localized to that locale. Given a translation key is missing for a locale, when rendering occurs, then the system falls back to en-US and logs a missing-key warning. Given a notification contains times, when rendered, then times are expressed in the recipient’s timezone and formatted per locale conventions. Given a recipient updates their locale preference, when subsequent notifications are sent, then they use the new locale.
Consolidated Month-End Digest Delivery
Given it is the last calendar day of the month at 17:00 in the recipient’s timezone and digest is enabled, when the digest job runs, then a single consolidated digest is delivered per recipient per enabled channel. Given the digest is delivered, when the recipient opens it, then it lists all credits in their scope expiring in the next 30 days grouped by team/policy and includes deep links to schedule backlog and policy settings. Given threshold alerts (14/7/3) and the digest fall on the same day, when notifications are sent, then batching and rate limiting ensure no more than 3 messages per channel per recipient that day and quiet hours are respected.
Deep Links to Backlog and Policy With Permission Gating
Given a recipient clicks the “Schedule Backlog” link from a notification and is authenticated and authorized, when the app loads, then they land on the backlog view filtered to credits expiring within the linked window for their teams. Given the recipient is not authenticated, when they click any deep link, then they are redirected through SSO and returned to the intended destination upon success. Given a recipient lacks permission to modify policies, when they click the “Adjust Policy” link, then they see an access-denied page with guidance to request access and no sensitive policy data is exposed. Given deep links include context parameters (bundle IDs, policy ID, threshold), when the destination loads, then those filters are applied and visible in the UI.
Webhook Emission for Upcoming-Expiration Events
Given an upcoming-expiration occurs for a bundle at a configured threshold and a webhook subscription is active, when the event is generated, then a POST is sent within 5 minutes containing event_id, event_type, org_id, bundle_id, threshold, expires_at, and affected_recipient_ids. Given the subscriber responds with 5xx or times out, when delivery is attempted, then retries occur up to 3 times with exponential backoff and an Idempotency-Key header is included to allow deduplication. Given a shared secret is configured, when the POST is sent, then an HMAC-SHA256 signature header is included and valid within a 5-minute clock-skew window. Given an admin triggers a replay from the UI, when executed, then the same payload with the same idempotency key is re-sent and the replay is logged.
Delivery Outcomes Logging and Compliance Reporting
Given any notification attempt occurs, when logging is performed, then an immutable entry is stored with timestamp, org_id, recipient_id, channel, notification_type (threshold|digest), bundle_ids, threshold, outcome (sent|delivered|deferred|rate_limited|bounced|failed), provider_message_id (if applicable), retry_count, and dedupe_key. Given an admin exports compliance logs for a date range, when the export is requested, then CSV and JSON files are generated within 60 seconds containing the selected fields and filters (channel, outcome, recipient, policy) and are available for download. Given log retention policy is enforced, when retention runs, then entries are kept for at least 24 months and purged thereafter with a record of purge actions. Given a delivery fails (e.g., bounce, invalid webhook signature), when the failure is detected, then the error detail is recorded and visible in the admin UI with filtering and drill-down.
Billing & Invoicing Alignment
"As a finance owner, I want invoices and billing calculations to reflect rollover accurately so that rates remain predictable and our revenue reporting stays compliant."
Description

Aligns rollover outcomes with billing to maintain stable, predictable after-hours rates. Invoices itemize carried-over credits, expirations, and current-cycle allocations, with clear proration when plans change mid-cycle. Ensures revenue recognition rules are respected (e.g., rolled-over credits are not new revenue), supports tax/VAT implications, and exposes line-item metadata for finance exports. Prevents rollover from altering contracted rates while accurately applying overage rules against post-rollover balances. Includes edge-case handling for partial periods and migrations.

Acceptance Criteria
Invoice Itemization for Rollover and Allocations
Given an account with previous-cycle unused credits that rolled over with a defined cap and expiry And a new billing cycle has started When the monthly invoice is generated Then the invoice shall include distinct line items for: Carried Over Credits, Expired Credits, Current-Cycle Allocation, and Usage Overage (if any) And each line item shall display quantity, unit rate, subtotal, and applicable tax And Carried Over Credits line subtotal shall be $0.00 and flagged as non-revenue And Expired Credits line quantity shall reflect credits removed and subtotal $0.00 And Current-Cycle Allocation unit rate shall equal the contracted rate and not be altered by rollover And the invoice shall show opening balance, credits added, credits expired, credits used, and closing balance fields
Mid-Cycle Plan Change Proration with Rollover
Given a customer changes plan mid-cycle and has a rollover balance with cap and expiry rules When the next invoice is generated Then proration line items shall be included with start/end timestamps and pro-rated quantities And the unit rate on proration lines shall equal the contracted rate before and after the change, unaffected by rollover And rollover caps and expiries effective after the plan change shall be applied to compute the carryover to the next cycle And the invoice shall include an adjustment line item for any reclaimed or forfeited credits due to cap or expiry changes with $0.00 subtotal And overages in the change period shall be calculated against the post-rollover balance at the applicable overage rate
Revenue Recognition and Classification of Rollover and Overage
Given rolled-over credits originate from prior billed periods When revenue reports and ledger exports are generated from the invoice Then rolled-over credits shall be classified as non-revenue and excluded from current-period recognized revenue And current-cycle allocations shall be recognized according to the revenue recognition schedule for the service period and not accelerated by rollover events And overage usage charges shall be recognized in the period of usage And all line items shall carry revenue_category metadata values of non_revenue, deferred, or recognized as applicable
Tax and VAT Treatment for Rollover, Allocation, and Overage
Given the customer's tax profile and jurisdiction are known When an invoice includes carried-over credits, current-cycle allocation, and any overage Then tax shall be computed only on taxable revenue line items according to jurisdiction rules And Carried Over Credits and Expired Credits lines shall have zero tax And the invoice shall itemize tax amounts per line item and a total tax amount And if a valid VAT ID qualifies for reverse charge, VAT shall not be applied and the invoice shall include required reverse charge wording And tax_rate_id and tax_jurisdiction metadata shall be present per line to support audits
Finance Export with Line-Item Metadata
Given finance requests a CSV and JSON export of invoices When the invoice is exported Then each line item shall include metadata fields: credit_type, rate_plan_id, contract_rate, usage_quantity, opening_balance, closing_balance, rollover_expiry_date, cap_applied, migration_reference, revenue_category, tax_rate_id And exported files shall validate against versioned schemas invoice.v1 (JSON) and invoice_lines.v1 (CSV) And numeric fields shall use dot decimal separators and ISO 4217 currency codes And line item and invoice IDs shall be stable and idempotent across repeated exports
Overage Calculation Against Post-Rollover Balance
Given an account starts a cycle with carried-over credits and receives a new allocation And credits expire mid-cycle per expiry policy When usage is recorded throughout the cycle Then usage shall first decrement carried-over credits until they expire, then current-cycle credits, before incurring overage And overage shall accrue only when the post-rollover balance reaches zero and shall be priced at the contracted overage rate And the invoice shall include a usage audit section with timestamps demonstrating the depletion order
Partial Periods and Plan Migration Edge Cases
Given a customer migrates from a legacy plan to a new plan with different credit units mid-cycle When the migration is executed Then credit balances shall be converted using a documented conversion factor and recorded as a $0.00 non-revenue adjustment line And duplicate rollover of the same credits shall be prevented via an idempotent migration_reference And expiries from the legacy plan shall be translated to the nearest supported expiry in the new plan and displayed on the invoice And an audit trail entry shall capture actor, timestamp, change details, and before/after balances
Fairness Scheduler Integration
"As a scheduling lead, I want the scheduler to consume rolled-over credits fairly and transparently so that we reduce after-hours meetings without compromising equitable rotation across time zones."
Description

Integrates rolled-over credits into the scheduling fairness engine using FIFO consumption to reduce after-hours burden without biasing rotations across time zones. Enforces safeguards such as per-cycle usage caps from rollover to prevent over-reliance, and surfaces balance/expiry context in scheduling UIs so leads can plan equitable sessions. Ensures policy changes are reflected in scheduling decisions within one cycle and that simulations inform scheduling forecasts.

Acceptance Criteria
FIFO Rollover Consumption in Fairness Engine
Given an account with rollover lots L1=10 credits (created 2025-06-01, expires 2025-09-30) and L2=5 credits (created 2025-07-01, expires 2025-10-31) and current_cycle=8 credits When 12 credits are consumed for scheduled meetings within work windows Then the engine debits 10 from L1 and 2 from L2, leaving current_cycle untouched And when any lot is past its expiry, Then those credits are ineligible for consumption and excluded from fairness calculations And all consumptions emit an audit record with fields: meeting_id, credit_source (rollover/current), lot_id, consumed_amount, remaining_amount, timestamp And retries with the same meeting_id are idempotent, producing no double-debit
Per-Cycle Rollover Usage Cap Enforcement
Given policy rollover_usage_cap is the lesser of 30% of total cycle consumption or 12 credits and available balances are rollover=20, current=40 When scheduling attempts to consume 18 rollover credits within the same cycle Then the engine stops rollover consumption once 12 credits have been used from rollover and draws remaining credits from current And no further rollover credits are consumed in that cycle after the cap is reached And the UI displays "Rollover cap reached" with remaining_cap=0 and shows the next cycle reset datetime And a CAP_ENFORCED audit event is recorded with policy_version and applied_sources
Unbiased Rotation Across Time Zones
Given 12 participants evenly distributed across Americas, EMEA, and APAC time zones and a 4-week cycle When the fairness engine schedules 24 recurring sessions using rollover credits (FIFO) under the configured cap Then per-participant after-hours sessions differ by ≤5% from the baseline engine without rollover And the Gini coefficient of after-hours burden across participants is ≤0.10 And the Pearson correlation |r| between time zone group and after-hours delta is ≤0.10
UI Surfaces Balance and Expiry Context
Given a lead opens the scheduling flow (create or edit) When the credit context API responds Then the UI shows: current_cycle_balance, rollover_balance, next_expiry_date (UTC), rollover_cap_used, rollover_cap_limit, and policy summary And values refresh within 5 seconds after an action that changes balances (e.g., meeting creation/cancellation) And tooltips define rollover, expiry, and cap; controls meet WCAG 2.1 AA for contrast and have ARIA labels And the GET /credits/context response includes fields with accurate values (±1 credit tolerance) and cache headers max-age ≤ 5s
Policy Change Propagation Within One Cycle
Given an admin updates rollover_expiry_days and/or rollover_usage_cap at time T0 When the next cycle boundary occurs Then all scheduling decisions on or after that boundary use the updated policy And simulations requested after T0 indicate effective_policy for current vs next cycle and adopt the updated policy for next-cycle forecasts within 10 minutes And existing scheduled meetings retain their original credit sourcing; an audit log records policy_version applied at scheduling time
Simulation Parity with Scheduling Decisions
Given a canonical dataset of 1,000 meetings across 3 cycles and a fixed policy When the simulation runs with the same inputs as the live scheduler Then predicted credit source (rollover lot vs current), cap application, and after-hours flag match actual outcomes with ≥97% overall accuracy and ≥95% precision and recall per dimension And any drift >3% over a 7-day window triggers an alert and is visible in the simulation accuracy dashboard
After-Hours Burden Reduction Under Rollover
Given historical constraints and a controlled A/B simulation for a 4-week cycle When comparing scheduling with rollover enabled vs disabled (all else equal) Then the variant with rollover shows a ≥20% reduction in average after-hours meetings per participant with 95% bootstrap CI not crossing 0 And rotation fairness metrics meet the thresholds defined in "Unbiased Rotation Across Time Zones"
Public API & Data Export
"As a data integrator, I want APIs and exports for balances, expirations, and ledger data so that I can automate reporting and align internal dashboards with TimeTether’s rollover outcomes."
Description

Provides REST endpoints to fetch current balances, upcoming expirations, and ledger entries with filters, sorting, pagination, and time window parameters. Adds a simulation endpoint that returns projected balances under proposed policy settings. Supports secure OAuth scopes, per-workspace rate limits, and webhook subscriptions for rollover and expiry events. Offers CSV exports for ledger and monthly summaries to integrate with external finance and analytics tools.

Acceptance Criteria
Security, OAuth Scopes, and Per-Workspace Rate Limits
Given a valid OAuth 2.0 token with scope credits.read for workspace W, When GET /workspaces/W/credits/balances, Then response is 200 and data contains only workspaceId=W Given a token missing the required scope, When requesting any Public API endpoint, Then response is 403 with error.code="insufficient_scope" and WWW-Authenticate includes required scopes Given an expired or invalid token, When calling any endpoint, Then response is 401 with error.code="invalid_token" Given two different workspaces A and B and a per-workspace limit of 100 requests/minute, When A sends 120 requests in a minute and B sends 50, Then A receives >=20 responses with 429 and B receives 0 responses with 429 Given a 429 response, Then headers include X-RateLimit-Limit=100, X-RateLimit-Remaining, and X-RateLimit-Reset epoch seconds
Fetch Current Balances
Given a workspace with credit buckets [base, rollover], When GET /workspaces/W/credits/balances, Then body includes totalCredits, availableCredits, rolloverCredits, holdCredits, currency, asOf (RFC3339 UTC), and perBucket breakdown with bucketId and type Given query parameter creditType=meeting, When GET balances, Then only matching credit types are included and totals reflect the filtered set Given Accept: application/json, When the request is successful, Then response includes Content-Type: application/json; charset=utf-8 Given an unknown workspaceId, When requesting balances, Then response is 404 with error.code="workspace_not_found" Given a dataset of at least 10,000 credits, When retrieving balances under 100 RPS for a single workspace, Then p95 latency <= 300 ms and p99 <= 600 ms
Retrieve Upcoming Expirations
Given credits with various expiration dates, When GET /workspaces/W/credits/expirations?windowStart=2025-10-01T00:00:00Z&windowEnd=2025-12-31T23:59:59Z&limit=100, Then response contains only entries with expirationAt within the window, sorted ascending by expirationAt, with at most 100 items Given the result set exceeds limit, When the response includes nextPageToken, Then a subsequent request with pageToken returns the next contiguous page with no duplicates or gaps Given an invalid time window where windowEnd < windowStart, When the request is made, Then response is 422 with error.field="windowEnd" and error.code="invalid_range" Given windowStart/windowEnd include timezone offsets, When the request is processed, Then the server normalizes to UTC while preserving the intended interval boundaries
Ledger Entries: Filters, Sorting, and Pagination
Given ledger entries of types [debit, credit, expiration, rollover], When GET /workspaces/W/credits/ledger?from=2025-07-01T00:00:00Z&to=2025-09-01T00:00:00Z&type=debit,credit&sort=-occurredAt&limit=50, Then response contains only entries in range and of requested types, sorted by occurredAt descending, with at most 50 items Given a nextPageToken from the first page, When requesting the second page with identical filters and sort, Then no items are repeated or skipped between pages Given an invalid sort field, When sort includes an unsupported value, Then response is 422 with error.field="sort" Given each ledger item in the response, Then it includes id, occurredAt (RFC3339), type, amount, balanceAfter, and policyId or bucketId when applicable, plus a metadata object
Policy Simulation Endpoint
Given POST /workspaces/W/credits/simulations with body {startDate, horizonMonths, rolloverCap, expiryDays, gracePeriodDays}, When the payload is valid, Then response is 200 with projectedBalances array length=horizonMonths and each item contains month, startingBalance, inflows, outflows, expirations, endingBalance, and rolloverLostDueToCap Given deterministic seed data, When running the same simulation twice with identical payload and Idempotency-Key header, Then responses are identical and share the same simulationId Given horizonMonths > 24 or < 1, When the request is made, Then response is 422 with error.field="horizonMonths" and reason="out_of_range" Given a payload missing required fields, When the request is made, Then response is 422 with error.details listing all missing fields
Webhook Subscriptions for Rollover and Expiry
Given POST /workspaces/W/webhooks with eventTypes ["credit.rollover","credit.expiry.upcoming"] and a valid targetUrl, When the request is valid, Then response is 201 with webhook id and secret Given an upcoming expiry or rollover occurs, When the webhook is active, Then delivery is attempted within 30 seconds, signed with header X-TimeTether-Signature: sha256=..., and payload includes eventId, eventType, occurredAt, workspaceId, and event-specific data Given the receiver returns a 5xx status, When retrying, Then exponential backoff is applied up to 8 attempts and a 2xx response stops further retries Given secret rotation is requested via PATCH /workhooks/{id}/rotate-secret, When the next event is delivered, Then the new secret is used and the old secret remains valid for up to 10 minutes overlap Given a webhook is paused, When events occur, Then no deliveries are attempted and events are not queued for the paused period
CSV Export for Ledger and Monthly Summaries
Given GET /workspaces/W/exports/ledger.csv?from=ISO8601&to=ISO8601, When successful, Then response is 200 with Content-Type: text/csv and Content-Disposition: attachment; filename contains the date range Given the same filters as the ledger API, When exporting ledger.csv, Then the number of data rows equals the count of ledger entries that match those filters in the JSON API Given GET /workspaces/W/exports/monthly-summaries.csv?year=YYYY, When successful, Then each row includes month, startingBalance, inflows, outflows, expirations, endingBalance, and rolloverCarried, and the totals per year are arithmetically consistent Given an export producing >100,000 rows, When the response is sent, Then it is streamed and completes without timeout, and Transfer-Encoding: chunked is present Given an unsupported export parameter, When the request is made, Then response is 422 with error.field matching the invalid parameter

Credit Buyback

Earn credits back by moving meetings into work hours, adopting fairness rotations, or canceling redundant sessions. Positive incentives encourage humane scheduling and create headroom for true exceptions.

Requirements

Credit Ledger & Balance
"As a remote team lead, I want a clear credit balance that updates whenever I make humane scheduling choices so that I can see progress and offset necessary exceptions."
Description

Implement a double-entry credit ledger that records accruals (moving meetings into team-defined work hours, adopting fairness rotations, canceling redundant sessions) and debits (scheduling exceptions like after-hours or skipping rotation). Ledger stores event, meeting ID, participants impacted, rule that awarded/debited, amount, timestamp, actor, and audit metadata. Support per-user and team balances, with rollups for org level. Provide idempotent transaction API to avoid duplicates on retries and reconciliation jobs to correct state if meetings are edited/canceled. Integrates with TimeTether calendar sync to trigger postings on schedule creation, reschedule, or cancellation. Expose balances to UI and API, with cache for fast reads and eventual consistency guarantees.

Acceptance Criteria
Idempotent Transaction Posting API
- Given a POST /ledger/transactions with idempotency_key K and payload P, When the request is retried with the same K and same P within 24 hours, Then only one transaction_id is persisted and subsequent calls return 200 with the same transaction_id and no additional ledger entries created. - Given a POST /ledger/transactions with idempotency_key K but a different payload P2, When processed, Then the API responds 409 Conflict and returns the original transaction_id and a payload_hash for P. - Given a POST /ledger/transactions without idempotency_key, When processed, Then the API responds 400 Bad Request. - Given concurrent POSTs with the same idempotency_key K, When processed, Then the outcome is equivalent to a single accepted request and no duplicate entries appear in the ledger.
Double-Entry Integrity and Immutability
- Given any persisted transaction, Then it contains two or more entries whose signed amounts sum to 0 and share a common transaction_id. - Then each entry includes non-null fields: entry_id, transaction_id, event_id, meeting_id, participants_impacted, rule_code, amount, timestamp_utc (ISO 8601), actor.type, actor.id (optional for system), audit.source, audit.request_id. - When a correction is needed, Then a new compensating transaction is appended and direct updates/deletes to existing entries are rejected with 405 Method Not Allowed. - Given invalid referential IDs (event_id or meeting_id), When inserting, Then the write is rejected with 422 Unprocessable Entity. - Then a unique constraint prevents duplicate entry_id and duplicate (transaction_id, entry_index).
Event-Driven Accruals and Debits
- Given a meeting is created that moves into all participants' team-defined work windows, When calendar sync ingests the event, Then an accrual transaction is posted with rule_code=move_into_work_hours and per-participant credits allocated. - Given a meeting is scheduled after-hours as an exception, When ingested, Then a debit transaction is posted with rule_code=after_hours_exception and amounts computed per policy. - Given a team adopts a fairness rotation, When the rotation is applied to scheduling, Then an accrual transaction with rule_code=adopt_fairness_rotation is posted. - Given a meeting is canceled as redundant, When cancellation is confirmed, Then an accrual transaction with rule_code=cancel_redundant_session is posted. - Given a rotation is explicitly skipped, When detected by the scheduler, Then a debit transaction with rule_code=skip_rotation is posted. - Then each transaction includes participants_impacted reflecting only the users affected by the specific rule event.
Balance Rollups (User, Team, Org)
- Given ledger entries within a date range [t1,t2], When calling GET /balances/users/{userId}?from=t1&to=t2, Then the balance equals the sum of that user's entry amounts within [t1,t2]. - Given a team T, When calling GET /balances/teams/{teamId}?from=t1&to=t2, Then the balance equals the sum of member user balances for the same period, excluding users not in T during that period. - Given an org O, When calling GET /balances/orgs/{orgId}?from=t1&to=t2, Then the balance equals the sum of all team balances in O for the same period. - When providing filters meeting_id or rule_code, Then only matching entries are included in the balance computation. - When requesting paginated results with limit and cursor, Then responses include a stable next_cursor and total_count for the query.
Reconciliation on Edits and Cancellations
- Given a meeting is edited changing time, timezone, or participants such that prior postings are inaccurate, When the reconciliation job runs, Then compensating transactions are posted to correct the net effect and prior transactions are linked via supersedes_transaction_id. - Given a meeting is canceled, When reconciliation runs, Then prior related accruals/debits are netted to zero via a compensating transaction set. - Given recurring meetings with series-level changes, When detected, Then only affected occurrences are reconciled and links to the parent series_id are preserved. - Given the reconciliation job is re-run for the same window, When executed, Then no additional net changes are produced (idempotent) and results are identical. - Given partial failure during reconciliation, When resumed, Then processing continues from the last durable checkpoint and each compensating transaction remains double-entry balanced.
Cached Balance Reads with Eventual Consistency
- Given recent transactions committed, When calling GET balance endpoints under normal load, Then p95 latency <= 100ms via cache. - When a new transaction posts affecting a scope (user/team/org), Then corresponding cache keys are invalidated within 1s and updated balances are visible within 10s (eventual consistency SLA). - Given a client includes If-None-Match with the latest ETag, When balances are unchanged, Then the API returns 304 Not Modified. - Given cache TTL is reached, When the next read occurs, Then values are refreshed from the ledger and the new ETag reflects the underlying version. - Given cache unavailability, When reading balances, Then the system serves from the source of truth with p95 latency <= 500ms and continues operation.
Participants Impacted and Rule Attribution
- Given a ledger entry for a meeting event, Then participants_impacted contains the unique user IDs actually affected by the rule at time of posting; removed users are excluded post-reconciliation. - Then rule_code must be one of: move_into_work_hours, adopt_fairness_rotation, cancel_redundant_session, after_hours_exception, skip_rotation; otherwise the write is rejected with 422 Unprocessable Entity. - When querying balances filtered by rule_code or meeting_id, Then only entries matching the filters contribute to the result and totals are accurate to within 1 credit of the raw ledger sum (0 tolerance for mismatch).
Real-time Credit Preview & One-click Apply
"As a product manager scheduling a cross-timezone meeting, I want to see how many credits I’ll earn before I confirm a time so that I can choose an option that minimizes after-hours impact."
Description

Show a real-time credit delta estimate in scheduling flows and suggestions, indicating how many credits would be earned (or spent) if the user selects a proposed time, enables rotation, or cancels a session. Surface the preview within one-click invite and reschedule modals, updating as the user hovers/selects times across timezones. On confirmation, convert preview to a ledger transaction and send updated invites. Handle edge cases like partial attendance changes, daylight saving shifts, and overlapping windows. Provide optimistic UI updates with fallback reconciliation from server events.

Acceptance Criteria
Real-time Credit Delta Preview in Invite/Reschedule Modals
Given a user opens the one-click invite or reschedule modal with attendees across timezones When the user hovers over or selects a proposed time slot Then a credit delta preview is shown within 500 ms displaying earned/spent credits with a signed value and unit And the value reflects attendees' local work windows for that slot Given the user toggles Enable Fairness Rotation on or off When the toggle state changes Then the credit delta preview recalculates within 500 ms to include or exclude rotation effects Given the user changes the candidate slot (date, start time, or duration) When the slot changes Then the preview updates within 500 ms and matches the server preview amount for the same inputs Given accessibility requirements When the preview is displayed Then it is readable by screen readers with the numeric delta exposed and has sufficient contrast
Suggestions Panel Shows Live Credit Deltas
Given scheduling suggestions are displayed for a meeting When the suggestions list renders Then each suggestion shows a credit delta badge for that option Given the participant set, work-hour preferences, or timezone context changes When suggestions refresh Then all suggestion deltas refresh within 1 s and match the server preview for their respective options Given a suggestion is focused or selected When the user focuses/selects a suggestion Then the consolidated preview area shows the same delta as the selected suggestion
One-click Apply Commits Ledger and Sends Updated Invites
Given a non-zero credit preview is visible for the selected action When the user confirms Send Invites or Apply Reschedule Then exactly one ledger transaction is created with an amount equal to the preview and linked to the meeting series/occurrence ID And updated invites/reschedules are sent to all attendees within 10 seconds Given optimistic UI is enabled When the user confirms Then the UI immediately reflects the applied delta and updated time And if the server result differs, the UI reconciles to the server value within 3 seconds and surfaces a notice Given any commit or invite operation fails When a failure occurs Then optimistic changes are reverted, a clear error message is shown, and no ledger transaction remains committed
Cancel Session Shows Credit Refund Preview and Applies on Confirm
Given a user chooses Cancel session for a meeting When the cancel modal opens Then a credit delta preview is displayed showing the credits earned by cancellation Given the user confirms cancellation When the action is submitted Then the calendar event is canceled for all attendees, a ledger transaction equal to the preview is created, and cancellation notifications are sent Given the cancellation cannot be completed When the server rejects the action Then any optimistic UI changes are rolled back and no credits are applied
Partial Attendance Changes Recalculate Credits
Given the attendee list is modified (add, remove, or mark optional) before confirmation When attendees are changed Then the credit preview recomputes within 500 ms using the current invited set and their work windows Given an attendee is marked optional or removed When the change is applied Then the preview and final ledger exclude that attendee's burden per policy and match the server result Given attendees respond asynchronously after confirmation in a way that changes credit outcomes When the server emits reconciliation events Then the ledger is adjusted to the correct amount within 3 seconds and the UI shows a change notice
Daylight Saving and Timezone Shift Accuracy
Given a candidate time falls on or near a daylight saving transition for any attendee When the user navigates across the boundary (e.g., day before vs day after) Then the preview aligns with each attendee's local work hours with no 60-minute offset errors Given known DST transition dates (e.g., US Pacific start/end, EU Central start/end) When selecting the same UTC time across those dates Then the preview amounts remain consistent with policy and match the server preview
Overlapping Windows Policy Consistency
Given a proposed slot partially overlaps non-working hours, existing meetings, or multiple defined work windows When the slot is selected Then the preview applies the configured overlap/proration policy and matches the server preview exactly for the same inputs Given multiple work windows exist for a participant on the same day When a slot spans two windows Then the preview calculation is split or prorated per policy, and the final ledger on confirmation equals the preview amount Given a conflict is introduced or removed for any attendee When the conflict state changes Then the preview delta updates within 500 ms to reflect the new state
Work-hours Incentive Engine
"As an engineering manager, I want credits to reflect how much a change helps the team’s work-life balance so that the rewards feel fair and motivate better scheduling."
Description

Create a rules engine that calculates credits for moving meetings into participants’ defined work windows relative to their timezones. Consider magnitude of improvement (minutes moved into window), number of participants helped, recurrence impact, and whether the change eliminates after-hours for any participant. Rules should be versioned and configurable per org policy (caps, multipliers for large groups, diminishing returns to prevent gaming). Integrate with calendar availability, TimeTether’s timezone model, and recurrence series adjustments. Emit explainability metadata so users and admins understand why a credit amount was awarded.

Acceptance Criteria
Credit Calculation: Minutes Moved Into Work Windows
Given a meeting move increases in-window minutes for one or more participants And each participant has a defined work window in their local timezone When the engine evaluates the change Then it computes base credits proportional to the total in-window minutes gained across all affected participants for the affected instance(s) And applies a recurrence multiplier across remaining future instances in the series And applies a bonus if any participant’s after-hours time is reduced to zero for the affected instance(s) And applies configured caps at per-instance, per-series, per-user, and per-org levels And applies configured rounding rules to produce a whole-number, non-negative credit And awards zero credits when net in-window minutes gained is less than 1 minute And never produces negative credits for any evaluation
Versioned, Org-Configurable Rule Sets
Given an org admin with permission to manage incentive rules When they publish a new rules version with caps, multipliers, diminishing returns, and an effective_at timestamp Then subsequent evaluations use the new version for events at or after effective_at And historical awards remain tagged with their original rule_version_id And the calculation response includes rule_version_id and org_policy_id And all rule changes are audit-logged with actor, diff, and timestamp
Timezone and Recurrence Correctness
Given participants are in multiple timezones with upcoming DST transitions And the meeting is part of a recurring series When the meeting time or recurrence pattern is changed Then the engine computes in-window minutes per participant per instance using TimeTether’s timezone model And accounts for DST shifts on future instances when calculating in-window minutes And recalculates across all remaining future instances impacted by the change And completes evaluation for a 52-instance series with 10 participants within 2 seconds at p95
Explainability Metadata Emission
Given a credit calculation completes When the result is persisted and exposed via API/UI Then the metadata includes rule_version_id, org_policy_id, base_minutes_gained, participants_helped_count, instances_evaluated, after_hours_eliminated_user_ids, multipliers_applied, caps_applied, diminishing_returns_factor, final_credit, time_basis, and correlation_id And the user-facing view includes at least base_minutes_gained, participants_helped_count, after_hours_eliminated_user_ids, and final_credit with a plain-language reason And the admin view includes the full metadata set And the API returns the calculation with metadata within 300 ms for a single evaluation fetch
Anti-Gaming: Diminishing Returns and Oscillation Handling
Given repeated meeting moves occur within the configured lookback window When successive moves yield incremental improvements toward the same final time Then diminishing returns reduce marginal credits according to the configured curve And oscillating or net-zero changes within the lookback window do not earn additional credits beyond the highest prior award And a unique improvement state is credited at most once per series within the lookback window And reversions that decrease in-window minutes earn zero credits and reset the improvement state after the lookback window elapses
Triggers, Recalculation Scope, and Idempotency
Given events include meeting time edits, recurrence edits, participant work-window changes, or participant timezone changes When any such event occurs Then the engine recalculates credits only for the affected instance(s) or series And emits at most one award per unique event using an idempotency key And ignores duplicate or retried events for award issuance And applies the rule version in effect at evaluation time unless an org policy effective_at override is specified And persists credits and metadata transactionally (all-or-nothing)
Calendar Availability Validation for Eligible Moves
Given connected calendars define participant availability and meeting attendance rules (required vs optional) When evaluating a proposed meeting move Then the engine validates that target time slots meet availability for required participants per quorum policy And excludes credits for moves that exceed allowable conflict thresholds And uses the post-move scheduled time to compute credits And applies configured participation rules for partial attendance scenarios to determine eligibility and participant counts
Redundant Session Detection & Cancel Credit
"As a team lead, I want to earn credits for canceling redundant meetings so that my calendar has more focus time and fewer interruptions."
Description

Detect redundant or low-value recurring sessions by analyzing attendance patterns, agenda duplication, and proximity to related meetings, then recommend cancellations or consolidation with an associated credit award upon confirmation. Use heuristics and lightweight ML signals (e.g., no decisions recorded, repeated empty agendas, low attendance quorum) with admin-tunable thresholds. Provide safe-guarding: preview impact, notify attendees, and schedule follow-up async survey to confirm redundancy. Upon cancellation, post credits to the ledger and clean up calendar artifacts. Respect privacy and only analyze metadata the org allows.

Acceptance Criteria
Heuristic Redundancy Scoring and Flagging
Given a recurring meeting with >= 6 past occurrences in the last 8 weeks and the org allows analysis of title, time, attendance counts, agenda presence, and decision tags And admin defaults are set: min_attendance_quorum = 60%, max_consecutive_empty_agendas = 3, min_decisions_rate = 10%, candidate_threshold = 0.75 When the redundancy analysis job runs hourly or is triggered on-demand Then the meeting receives a redundancy_score between 0 and 1 And the meeting is marked Redundant Candidate if redundancy_score >= candidate_threshold And disallowed metadata is never accessed and a privacy-safe audit log entry is recorded with signal values and timestamp
Impact Preview and Consolidation Recommendation
Given a meeting marked as Redundant Candidate When a reviewer opens the recommendation Then the UI displays redundancy_score, top 3 contributing signals with their values, stats for the last 8 occurrences, list of related meetings within ±24 hours with attendee overlap %, estimated monthly hours saved, and projected credit amount And the reviewer can simulate Cancel vs Consolidate (choose a target series) and see impact metrics and credit amount update prior to confirmation
Reviewer Confirmation, Attendee Notification, and Survey Scheduling
Given a reviewer selects Cancel or Consolidate for a candidate meeting When they click Confirm Then the system presents an editable attendee notification (subject, rationale, effective date) and a scheduled follow-up async survey for T+14 days using the Redundancy Confirmation template (2 questions) And upon send, notifications are delivered to 100% of invitees with delivery statuses captured (success/fail) And a rollback option is available for 30 minutes to revert the action and restore the series
Calendar Artifact Cleanup Post-Cancellation
Given a confirmed cancellation of a recurring series effective on date D When the cleanup job runs within 5 minutes Then all future instances after D are canceled across all connected calendars; conferencing and room resources are released; reminders are removed And past instances remain unchanged; attached documents/links in descriptions are preserved And ICS/webcal feeds and integrations reflect the change within 10 minutes And a cleanup report is saved with counts of instances updated and resources released
Credit Ledger Posting and Balance Update
Given a confirmed Cancel or Consolidate action is executed When hours_saved are computed for remaining occurrences over the next 90 days within work windows and adjusted for consolidation overlap Then credits are posted to the ledger with idempotency_key, meeting_id, action, hours_saved, credit_amount, timestamp, actor, and link to audit log And the actor’s and team’s credit balances update within 1 minute of posting And duplicate postings are prevented; failures trigger alerting and automatic retry with exponential backoff (max 3 attempts)
Admin-Tunable Thresholds and Signal Configuration
Given an admin updates redundancy thresholds and allowed signals in Settings When they save the configuration Then inputs are validated (percentages between 0–100, integer counts >= 1, required dependencies satisfied) And changes propagate to analysis jobs within 10 minutes and are versioned And existing Redundant Candidates are re-scored within 24 hours And a change log entry records before/after values, editor, and timestamp
Privacy Controls and Data Access Guardrails
Given the org disables analysis of specific signals (e.g., agendas or decisions tags) When the redundancy analysis runs Then those signals are excluded and shown as Disabled by policy in the UI And no content beyond allowed metadata is read, stored, or logged And a privacy attestation is displayed to reviewers with a link to the active data-access policy And automated access-control tests verify disallowed fields are blocked in analysis and logging
Fairness Rotation Adoption Rewards
"As a distributed team owner, I want to be rewarded for turning on fairness rotations so that the burden of inconvenient times is shared equitably."
Description

Award credits for enabling or adhering to fairness-driven rotation for recurring meetings that rotate burden across timezones. Detect adoption (toggle enablement, adherence across N occurrences) and provide bonus credits when rotations eliminate consistent after-hours for any one cohort. Handle exceptions like holidays, DST, and manual overrides without penalizing compliance when rules allow. Surface adherence status and upcoming rotation schedule in the meeting detail view with earned-to-date credits.

Acceptance Criteria
Enable Fairness Rotation Awards Base Credits
Given a recurring meeting without fairness rotation enabled When an owner enables the fairness rotation toggle and saves settings Then the system records rotation_enabled=true with timestamp and policy_version And awards BASE_CREDIT to the meeting's credit ledger exactly once And subsequent edits that keep rotation enabled do not award additional base credits And disabling then re-enabling only awards base credit again if ENABLE_REENABLE_AWARD=true and COOLDOWN_DAYS has elapsed; otherwise no additional base credit is awarded And the UI shows a confirmation and updated credit total within 5 minutes
Adherence Across N Occurrences Earns Credits
Given fairness rotation is enabled and a rotation schedule is published And ADHERENCE_N and PER_OCCURRENCE_CREDIT are configured When each scheduled occurrence ends within the assigned cohort's defined work window for that occurrence (respecting participant timezones) Then award PER_OCCURRENCE_CREDIT for that occurrence within 10 minutes of the scheduled end time And canceled occurrences do not earn or penalize credits And reschedules outside the assigned work window and beyond TOLERANCE_MIN are marked noncompliant and earn no credit And the adherence streak resets upon a noncompliant occurrence And credits are not granted more than once per occurrence (idempotent)
Bonus Credits for Eliminating Consistent After-Hours
Given a baseline over BASELINE_WINDOW_WEEKS shows at least one cohort exceeded AFTER_HOURS_THRESHOLD for the meeting pre-adoption When T consecutive rotations complete post-adoption with zero occurrences outside work hours for any previously impacted cohort Then award BONUS_CREDIT once per meeting within 10 minutes of confirming the Tth compliant rotation And do not award additional bonus unless a new baseline period re-establishes the problem and it is subsequently eliminated again And calculations respect each cohort’s timezone and work window definitions
Exceptions: Holidays, DST, and Manual Overrides
Given an occurrence falls on a configured holiday for the assigned cohort When the system skips or shifts that occurrence according to rotation rules Then mark the occurrence compliant for adherence calculations and do not penalize credits Given a DST transition affects local time When the meeting time shifts within DST_TOLERANCE_MIN relative to the assigned work window Then the occurrence remains compliant Given a manual override is applied with Allow Exception enabled and within ALLOWED_EXCEPTIONS_PER_QUARTER When the occurrence is held outside the assigned window Then count it as compliant; otherwise mark noncompliant
Meeting Detail View: Adherence, Schedule, and Credits
Given a user opens the meeting detail view for a recurring meeting When fairness rotation is enabled Then display: Rotation status, next 3 scheduled occurrences with assigned cohort and local times, last ADHERENCE_N occurrences with Pass/Fail and reasons, credits earned-to-date (by type) and next potential award And values update within 5 minutes after an occurrence ends or settings change And displayed totals reconcile with the credit ledger for the meeting And a link to view full rotation schedule over the next 90 days is present
Credit Ledger Integrity and Idempotency
Given any credit is granted (base, per-occurrence, or bonus) Then create a ledger entry with: meeting_id, occurrence_id (nullable), credit_type, amount, policy_version, event_timestamp, idempotency_key, actor=system And repeated processing with the same idempotency_key does not create duplicate entries And the meeting’s credit balance equals the sum of its ledger entries And an API endpoint GET /meetings/{id}/credits returns the ledger and computed totals within 200ms p95 for meetings <= 200 occurrences
Rotation Disable and Re-enable Behavior
Given fairness rotation is currently enabled When an owner disables the rotation Then cease computing future per-occurrence and bonus credits starting with the next unsent occurrence And retain all previously earned credits and ledger entries And the meeting detail view updates to show Rotation: Disabled and hides the upcoming rotation schedule When rotation is re-enabled Then resume the rotation from the next valid slot, recompute adherence baseline forward-only, and award base credit per ENABLE_REENABLE_AWARD policy
Admin Policies, Caps & Reporting
"As an org admin, I want to configure credit policies and report on outcomes so that incentives align with company norms and I can demonstrate impact."
Description

Provide admin controls to define credit policies (earning rules versions, per-user/team caps, expiry, redemption mapping to exception allowances), abuse protections (rate limits, duplicate reschedule detection, minimum delta thresholds), and audit access. Offer dashboards showing credits earned vs. spent, after-hours minutes reduced, and top contributing teams; exportable via CSV/API. Include policy change logs, retroactive recalculation tools when rules update, and data retention settings to meet compliance. Integrate SSO roles for who can view/modify policies and balances.

Acceptance Criteria
Credit Policy Configuration: Rules Versions, Caps, Expiry, Redemption
- Admins with Policy Admin role can create a policy version defining earning rules, per-user and per-team caps, credit expiry, and redemption mappings to exception allowances. - Saving validates required fields, value ranges, and unique version name; invalid configs block save with explicit error messages naming the invalid fields. - A policy version can be set to Active immediately or Scheduled with an effective UTC timestamp; only one active version applies per scope (org or team) at any time. - Policy scope assignment supports org-wide or specific team targeting; conflicts resolve by most-specific scope precedence. - A preview computes expected credits for a selected user and date range under the proposed policy prior to activation. - The active policy is applied to new credit events within 1 minute of activation, confirmed by a test event reflecting the new rules.
Abuse Protections Enforcement: Rate Limits, Duplicate Detection, Minimum Delta
- Configurable rate limits cap credit-earning events per user and per team within a rolling window; excess events are rejected with reason "rate_limit" and are visible in an admin review log. - Duplicate reschedule detection suppresses awarding multiple credits for the same meeting when repeated reschedules occur within a configurable window; only the first qualifying event earns credits, later ones log reason "duplicate". - A minimum delta threshold requires reschedules to move at least N minutes toward the user’s work window to earn credits; below-threshold events log reason "below_min_delta" and award 0 credits. - Abuse decisions are logged with timestamp, actor, meeting ID, rule triggered, and outcome; logs are filterable by rule and date range. - Boundary tests (exactly at limit, just under/over thresholds) demonstrate correct allow/deny outcomes.
SSO Role-Based Access Control for Policies and Balances
- SSO group-to-role mapping assigns: Policy Admin (create/edit policies, run recalculations), Auditor (read-only logs/dashboards), Team Lead (view team balances), Member (view own balance). - Unauthorized actions return HTTP 403, display an inline error in UI, and are recorded in an access log with user ID, action, and resource. - Role changes in the IdP take effect within 15 minutes without requiring user reauthentication. - Policy edit UI and policy/credits admin APIs are accessible only to Policy Admins; team balance views are restricted to Team Leads for owned teams.
Reporting Dashboard: Credits Earned vs Spent, After-Hours Minutes Reduced, Top Teams
- Dashboard supports date range and team filters; applying filters updates results in under 2 seconds for orgs up to 5,000 users. - Metrics displayed: total credits earned, total credits spent, net balance, after-hours minutes reduced, and a ranked list of top 10 contributing teams by selected metric. - Aggregates match underlying transactions for identical filters within max(0.1%, 1 unit) variance. - All timestamps and minutes render in the org default timezone with a UTC tooltip; no-data ranges render as zeros without errors.
Data Export Interfaces: CSV and API
- CSV export available for dashboard views and raw transactions with defined columns, UTF-8 encoding, org timezone timestamps, and includes filter context in header row. - Exports respect RBAC; users can only export data visible to them in the UI. - API provides GET /credits/transactions and GET /reports/credits with OAuth2/SSO auth, pagination, and filters (date range, team, user); valid requests return 200 with documented JSON schema; invalid params return 400 with error codes. - Exported row counts and totals for a given filter match the dashboard within max(0.1%, 1 unit) variance. - Streaming/pagination supports exporting 1,000,000 rows within 30 minutes without timeouts.
Policy Update Workflow: Change Logs, Audit Trail, Retroactive Recalculation
- Every create/update/delete of a policy version records an immutable log entry with actor, UTC timestamp, before/after values, reason, and scope; entries are searchable and exportable. - Audit views are accessible to Auditors and Policy Admins and support filtering by actor, action, date range, and policy version; others receive 403. - On activating a new policy version, Admins can run a dry-run retroactive recalculation for a selected date range and scope showing estimated impacted users and total credit delta. - Executing recalculation creates per-user adjustment transactions with positive/negative deltas and reason "policy_update", guarantees idempotency via job ID, and prevents double-counting on retries. - Job progress and completion status are visible; completion notifies Admins; failures are retried up to a configurable limit and surfaced with actionable error messages.
Data Retention & Compliance Settings
- Admins configure retention periods per data category (transactions, logs, aggregates) within allowed bounds; UI shows effective deletion dates by category. - A scheduled process enforces retention by deleting or anonymizing expired records; referential integrity is preserved and dependent aggregates are recomputed. - Legal holds can be applied by scope (user/team/date range) to suspend deletion; holds are audited and override retention until removed. - All deletions/anonymizations are logged with counts and timestamps and included in an exportable retention report. - A compliance page displays current retention settings and last enforcement run status; failures trigger alerts to Policy Admins.

Ledger Heatmap

A real-time, color-coded ledger that visualizes spend by region, team, and series with drill-down to specific meetings. Exposes hotspots and trends so owners can intervene quickly and transparently.

Requirements

Real-time Data Ingestion & Normalization
"As a data consumer of TimeTether, I want meeting and participant data normalized in real time so that the heatmap always reflects accurate, current spend and after-hours impact."
Description

Continuously ingest meeting data from TimeTether’s scheduling graph and connected calendars to construct a unified ledger of meetings, participants, durations, timezones, regions, teams, and series. Normalize records into a canonical schema, enrich with computed attributes (participant-hours, after-hours flags based on local work windows, cost multipliers, owners), and de-duplicate updates. Ensure sub-minute latency via incremental change streams and idempotent upserts, with backfill jobs for historical windows. Provide resiliency with retry policies, dead-letter queues, and monitoring to guarantee data completeness and freshness for the heatmap.

Acceptance Criteria
Sub-minute Incremental Ingestion Latency
Given meeting events emitted from the scheduling graph and connected calendars When events appear on the incremental change stream Then corresponding ledger rows are created or updated within 60 seconds P95 and 120 seconds P99 over any rolling 60-minute window And no individual event’s end-to-end latency exceeds 5 minutes And the ingestion_lag_p95 metric remains ≤ 60 seconds during steady state And latency SLOs are met for both sources: scheduling graph and each connected calendar provider
Canonical Normalization and Enrichment Accuracy
Given raw events containing start/end times, participants, region, team, series, owner, and source identifiers When records are normalized Then ledger rows conform to canonical schema v1.0 with required fields non-null and data types enforced And duration_minutes = rounded difference between end and start in minutes And participant_hours = duration_hours × count(participants) And after_hours_flag per participant is computed from local timezone and configured work window and aggregated to meeting-level fields And cost_total = participant_hours × cost_multiplier(region, team) And invalid or incomplete records are rejected with a validation error and are not written
Idempotent Upserts and De-duplication
Given duplicate deliveries of the same logical event (retries or replays) When processed Then only one ledger record exists per unique key {source_system, source_event_id, recurrence_instance_start_utc} And processing identical updates does not modify updated_at or create a new version And processing a newer update (greater source_updated_at) overwrites older values atomically And processing an older update is ignored without changing the ledger And dedup_rate metric is emitted and remains ≥ 99.9% for duplicates
Concurrent Backfill Without Impacting Real-time
Given a backfill job is started for a historical window up to 30 days When backfill runs concurrently with the change stream Then real-time ingestion latency remains within 60 seconds P95 and 120 seconds P99 And backfill throughput sustains ≥ 10,000 events per minute until caught up, with adaptive throttling to protect SLOs And backfill writes are idempotent and do not introduce duplicates And post-backfill reconciliation shows source vs ledger count deviation ≤ 0.5% at T+15 minutes And backfill supports checkpointing and resumes from the last successful offset after interruption
Retry Policy and Dead-letter Handling
Given a transient processing failure When an event fails to ingest Then the system retries with exponential backoff and jitter (initial 1s, max 60s, up to 10 attempts) and preserves order per key And on exceeding max attempts or encountering a permanent error, the event is sent to a DLQ with payload, error, and trace metadata And an alert triggers within 5 minutes if DLQ depth > 100 or DLQ growth > 20 per minute And operators can trigger DLQ reprocessing; on reprocess, ≥ 99% of DLQ messages succeed and residual failures are annotated for investigation
Monitoring, Freshness, and Completeness SLOs
Given continuous operation When observing system metrics Then dashboards expose ingestion_lag_p50/p95/p99, throughput, error_rate, dedup_rate, dlq_depth, backfill_lag, ledger_freshness, and completeness_deviation And alerts fire when p95_ingestion_lag > 60s for 5 consecutive minutes, error_rate > 1% for 5 minutes, or completeness_deviation > 0.5% over a 15-minute window And daily reconciliation per region, team, and series shows source vs ledger count deviation ≤ 0.5% at T+15 minutes, with discrepancies recorded in an audit log And heatmap queries read only committed ledger entries with freshness ≤ 60 seconds P95
Timezone and Work-window Correctness
Given a participant timezone and configured local work window When a meeting occurs outside the participant’s work window in their local time Then after_hours_flag=true for that participant and participant-hours are correctly attributed to after-hours aggregates And during DST transitions, local offsets are computed using current IANA tzdata and flags remain accurate And for multi-timezone meetings, after-hours computation is performed per participant and aggregates at meeting, series, team, and region levels are correct And timezone data is updated within 7 days of a new IANA release and verified by automated checks
Aggregation & Metrics Engine
"As an analytics-focused manager, I want accurate aggregated metrics by region, team, and series so that I can compare burdens and identify where intervention is needed."
Description

Compute rollups by region, team, and series across selectable time buckets (day, week, month) and metrics including total participant-hours, after-hours participant-hours, percentage after-hours, meeting count, unique participants, fairness rotation score, and trend deltas versus prior periods. Support multi-dimensional grouping and slicing with performant query plans, pre-aggregated materialized views, and incremental updates as new meetings arrive or are modified. Expose a query API to the UI with pagination, sorting, and numeric formatting, aligning with TimeTether’s fairness rules and work-window configurations.

Acceptance Criteria
Rollup correctness across dimensions and time buckets
Given a seeded dataset of meetings across multiple regions, teams, series, timezones, and dates spanning partial and full days/weeks/months When the API is queried with groupBy combinations of [region], [team], [series], [region,team], [team,series], and timeBucket in {day, week, month} over a defined time range Then totals for metrics {totalParticipantHours, afterHoursParticipantHours, pctAfterHours, meetingCount, uniqueParticipants, fairnessRotationScore} exactly match the fixture’s expected results for each group and bucket And pctAfterHours is computed as afterHoursParticipantHours / totalParticipantHours with internal precision >= 4 decimal places (no rounding applied until formatting layer) And only groups with at least one meeting in the range are returned by default
Metric computation definitions and edge cases
Rules: - totalParticipantHours = sum of attendedMinutes/60 across all participants in all meetings within the group and range - afterHoursParticipantHours = sum of attendee minutes that fall outside that attendee’s configured work window in their local timezone (pro-rated at window boundaries), divided by 60 - pctAfterHours = afterHoursParticipantHours / totalParticipantHours; if totalParticipantHours == 0 then 0 - meetingCount = count of non-canceled meeting instances within the time range - uniqueParticipants = distinct count of participant IDs across all meetings in the group and range - fairnessRotationScore equals the reference algorithm’s output for the same input within ±0.01 tolerance Edge cases: - Daylight Saving Time transitions produce neither negative nor duplicate hours; local work windows shift correctly - Zero-duration or fully canceled meetings contribute 0 to all metrics and are excluded from meetingCount - Partial attendance is pro-rated using actual attendance intervals per participant
Incremental updates on create/modify/cancel
Given streaming events for meeting created/updated/canceled and attendance changes When an event that affects existing aggregates is received Then all impacted aggregates and materialized views are updated within 30 seconds of event receipt And repeated delivery of the same event does not change results (idempotent) And out-of-order or late events up to 90 days old correctly backfill prior buckets and update trend deltas And concurrent events for the same entity yield the same final aggregates as if processed serially
Trend deltas versus prior period
Given a selected time range and timeBucket in {day, week, month} When requesting trend deltas Then for each group and metric the API returns deltaAbs and deltaPct comparing to the immediately preceding period of equal length aligned to the bucket And if prior value == 0 and current == 0 then deltaAbs = 0 and deltaPct = 0 And if prior value == 0 and current > 0 then deltaAbs = current and deltaPct is null And partial current periods compare to equally partial prior periods
Query API pagination, sorting, filtering, formatting
Given a dataset with more than 5,000 groups When requesting results with pagination Then the API supports cursor-based pagination with pageSize between 10 and 500 and returns nextCursor when additional data remains And sorting by any metric or dimension is supported in ascending and descending order and is stable for ties using dimension keys And filtering by time range, region(s), team(s), and series ID(s) is supported And responses include both raw and formatted fields: - raw: hours as decimals with up to 4 decimal places; counts as integers; pct fields as decimals in [0,1] - formatted: hours with 1 decimal place and thousands separators; pct with 1 decimal place and a % sign And after-hours classification uses the current work-window configuration for each user/team at query time
Performance and materialized views utilization
Given a production-scale dataset (~2M meeting instances and ~20M attendance records over 12 months) When issuing common queries (e.g., top 100 groups) with warm cache Then p95 latency <= 800 ms and p99 <= 1500 ms at 50 concurrent requests And for cold-start queries p95 <= 2500 ms And materialized views exist for day, week, and month buckets and are updated incrementally with freshness lag <= 60 seconds And telemetry includes usedMaterializedView=true for day/week/month queries and false for ad-hoc ranges; EXPLAIN plans confirm MV usage
Drill-down retrieval for heatmap cells
Given an aggregated cell identified by [dimension keys, timeBucket, time range] When requesting drill-down Then the API returns a paginated list of constituent meetings including meetingId, start/end times, region, team, seriesId, attendeeCount, participantHours, afterHoursParticipantHours And the sum of participantHours and afterHoursParticipantHours over returned meetings equals the aggregate cell totals within 0.01 hours tolerance And results respect the same filters and sorting as the parent query And page tokens remain stable (no data drift) for at least 5 minutes
Interactive Heatmap Visualization
"As a product lead, I want a clear, color-coded view of meeting spend and after-hours hotspots so that I can quickly spot problem areas at a glance."
Description

Render a responsive, color-coded heatmap that maps metric intensity to an accessible color scale with legends, tooltips, and configurable metric selection. Support light/dark themes, keyboard navigation, and WCAG AA contrast. Allow users to switch dimensions (e.g., regions on rows, teams on columns), choose time buckets, and pin views. Implement virtualization for large grids and graceful empty/error states, ensuring smooth performance on desktop and mobile within TimeTether’s web app.

Acceptance Criteria
Responsive Heatmap Rendering Across Devices
Given a complete dataset and viewport widths of 320px, 768px, 1024px, and 1440px When the user opens the heatmap in light and dark themes and resizes the viewport Then the heatmap fits the container without horizontal overflow And cells are at least 40x40px at 320px viewport and remain fully tappable/clickable And the legend, metric selector, and dimension controls remain visible or collapsible without overlap And theme toggling repaints the heatmap and legend within 200ms with no loss of contrast And layout reflows complete within 300ms after a resize event
Accessible Color Scale and Legend (WCAG AA)
Given the heatmap is rendered in light and dark themes When cells show minimum, median, and maximum intensities and the legend is displayed Then contrast between cell content/borders and adjacent colors meets WCAG AA (>= 4.5:1 for text, >= 3:1 for graphical objects) And the legend includes min/mid/max labels with units and a "No data" key And color encoding is supplemented by exact numeric values in tooltips and accessible names And the chosen palette maintains distinguishable steps under common color-vision deficiency simulations
Keyboard Navigation and Screen Reader Support
Given a keyboard-only user focuses the heatmap grid When they navigate with Arrow keys, PageUp/PageDown, Home/End, Tab/Shift+Tab, Enter, and Esc Then focus moves cell-by-cell and row/column-wise with visible focus indicators And Enter opens the focused cell tooltip and Esc closes it And the grid exposes role=grid with aria-rowcount, aria-colcount, aria-labelledby and cells include row/column headers And screen readers announce row label, column label, time bucket, metric value with units, and selection state
Dimension Switching (Rows/Columns Toggle)
Given regions are on rows and teams on columns with active filters When the user toggles to teams on rows and regions on columns Then the grid re-renders preserving filters and sort orders for each dimension And axis labels, tooltips, and accessible names update to the new orientation And the interaction latency p95 is <= 200ms for datasets up to 50x200 visible cells on target hardware
Metric and Time Bucket Selection
Given metrics (meeting_count, after_hours_ratio, spend_usd) and time buckets (daily, weekly, monthly, quarterly) are available When the user changes the metric or time bucket Then the client includes the selected metric and bucket in the data request And cells update values, color mapping, and tooltip formatting (e.g., % or currency) within 200ms after data arrival And the legend units, min/max labels, and gradient update to match the selection And "No data" cells display a distinct neutral style and are excluded from color scale calculations And the last selected metric and bucket persist for the session and on reload
Pin Views: Save, Persist, and Restore
Given a user has configured dimensions, filters, metric, time bucket, and palette When they click Pin View and name the view Then the configuration is saved locally and to the user profile and appears in the Pinned Views list And loading a pinned view restores the exact configuration, including scroll position, within 300ms after data arrival And pinned views persist across sessions and are available offline if cached data exists And updating a pinned view allows overwrite with confirmation
Virtualization, Performance, and Graceful Empty/Error States
Given datasets up to 5,000 rows by 52 columns and intermittent network conditions When the user loads and scrolls the heatmap horizontally and vertically Then only visible rows and columns are rendered (windowed/virtualized) And initial render completes within 1s after data arrival and scroll performance maintains >= 50 FPS on target devices And loading shows skeleton placeholders within 100ms; empty datasets show an empty state with explanatory text and a "Adjust filters" action And network errors display an inline error with Retry and are announced to assistive tech; partial data errors mark affected cells as "—" with tooltips And error/empty states do not shift layout more than 0.1 CLS and do not block navigation
Multi-level Drill-down Navigation
"As a team owner, I want to drill down from a hotspot to the exact meetings and participants involved so that I can take targeted action to reduce after-hours burden."
Description

Enable click-through from any heatmap cell to progressively detailed views: from aggregate (region/team/series) to the underlying series and then to individual meeting instances with attendees, local times, and after-hours indicators. Preserve applied filters and context across levels, provide breadcrumbs for navigation, and support deep links sharable within TimeTether. Ensure queries are efficient and paginated, and that sensitive details respect user permissions.

Acceptance Criteria
Drill-down from Heatmap Cell to Series and Meeting Instances
Given the Ledger Heatmap is displayed with at least one populated cell and an authenticated user with access to the data When the user clicks a populated cell representing a Region, Team, or Series Then the app opens the Series view scoped to the selected dimension and current time range and filters And the Series view lists only matching series with correct aggregate totals When the user clicks a series from the list Then the Meeting Instances view opens for that series within the selected time range And each meeting row shows meeting date/time, attendee count, attendees with their local times, and an after-hours indicator per attendee where applicable
Filter and Context Preservation Across Levels
Given the user has applied any combination of filters (date range, region(s), team(s), series search, after-hours toggle) When the user drills down from Heatmap -> Series -> Meeting Instances and navigates back via breadcrumb or browser Back Then all applied filters and the time range remain unchanged across all levels And totals and counts remain consistent with the selected filters And returning to the Heatmap re-highlights the originally selected cell and restores prior scroll position
Breadcrumb Navigation Across Drill-down Hierarchy
Given the user is on any drill-down level beyond the Heatmap Then a breadcrumb trail is displayed showing the hierarchy from Heatmap to the current level with human-readable labels When the user clicks any breadcrumb ancestor Then navigation occurs to that level with all filters, time range, and context preserved And the current level is indicated as the last, non-clickable breadcrumb
Deep Link Sharing Restores Full Context
Given the user is on any Heatmap, Series, or Meeting Instances view with filters and pagination state When the user copies the URL and opens it in a new session or shares it with another authorized user Then the application restores the exact view, including selected dimension or series, filters, time range, and current page of results And if the recipient lacks permission for any item, restricted fields are redacted or the view is scoped to permitted data without erroring And if the link is invalid or tampered, the user is safely redirected to the Heatmap with a non-blocking error message
Efficient, Paginated Loading for Series and Meetings
Given a Series or Meeting Instances list with more than 50 items under current filters Then results are paginated with a default page size of 50 and controls to move to next/previous pages And navigating pages preserves filters and sort order and does not duplicate or omit items And API responses for page loads complete within 2.0s at p75 and 4.0s at p95 for page size 50 in staging datasets of at least 10k items And the client renders the list within 500ms of API response and shows a loading state during fetch
Permission-based Access and Redaction of Sensitive Details
Given user permissions vary by region, team, series, and meeting visibility When a user drills down to a level they are not authorized to view Then the UI prevents access to unauthorized entities and shows a non-blocking permission message And for partially permitted entities, attendee names and emails are redacted while aggregate metrics, local times, and after-hours indicators remain visible And deep links to unauthorized content do not disclose restricted data in the URL or UI and return a 403 or a redacted view as appropriate
Advanced Filtering & Segmentation
"As an operations analyst, I want to segment the ledger by time window, teams, and after-hours criteria so that I can answer specific questions without manual data wrangling."
Description

Provide robust filters for date range, business hours definitions, region, team, meeting series, owner, participant role, and meeting type. Support saved filter sets, defaults based on the user’s org context, and include/exclude after-hours. Implement query param sync for shareable state and ensure filters cascade through heatmap, drill-down, and exports. Validate combinations to avoid heavy queries and surface fast, incremental results.

Acceptance Criteria
Filters Cascade Across Heatmap, Drill-down, and Export
Given no filters are applied on initial load When the view initializes Then default filters based on the user’s org context are applied and visible in the filter bar Given any combination of date range, business hours definition, region, team, meeting series, owner, participant role, meeting type, and include/exclude after-hours is selected When Apply is triggered Then the heatmap updates to include only meetings matching all selected filters And clicking a heatmap cell opens a drill-down showing only meetings matching the filters plus the cell’s bucket And the drill-down total count equals the heatmap cell value And exporting from this state contains only the filtered meetings and matches the drill-down total Given filters are changed When Apply is triggered Then the heatmap updates within 1.2 seconds at P95 and the drill-down first page loads within 1.5 seconds at P95 Given filter selections result in no matches When Apply is triggered Then the heatmap shows zero values and an empty-state is displayed in drill-down And Export is disabled
Saved Filter Sets and Defaults
Given a user has configured filters When the user saves the configuration with a unique name Then the saved set appears in the Saved Filters list and can be selected to restore the exact filter state Given a saved filter set exists When the user sets it as default Then subsequent visits auto-apply that set and display it as the active selection Given a saved filter set is modified by the user When Update is chosen Then the saved set is persisted with the new values and reapplying it restores the updated state Given a saved filter set exists When the user deletes it Then it no longer appears in the Saved Filters list and will not auto-apply Given saved filter sets are user-scoped When another user views Saved Filters Then they do not see sets saved by others Given a saved filter set is applied When the underlying data has not changed Then heatmap, drill-down, and export results match those seen when the set was originally saved
Business Hours Definition and After-hours Toggle
Given multiple business hours definitions are available for the org When the user selects a definition Then after-hours classification uses the selected definition for all calculations Given the include/exclude after-hours toggle is Off When filters are applied Then only meetings within business hours are included in heatmap, drill-down, and export Given the include/exclude after-hours toggle is On When filters are applied Then both business-hours and after-hours meetings are included Given the business hours definition or after-hours toggle is changed When Apply is triggered Then counts are recalculated accordingly and the heatmap updates within 1.2 seconds at P95
URL Query Param Sync and Shareable State
Given filters are adjusted by the user When a filter value changes Then the URL query string updates to reflect the full filter state without a page reload Given a URL containing valid filter parameters is opened When the page loads Then the view initializes to exactly the state encoded in the URL and the same results are displayed Given a URL contains unsupported or unauthorized parameter values When the page loads Then unsupported values are ignored, defaults are applied, and a non-blocking notice informs the user Given the user navigates using the browser back/forward buttons When the history state changes Then the filters and results update to match the URL Given a URL is shared with another authorized user who has equivalent access When they open the link Then they see the identical filters and results; if they have reduced access, restricted filters are dropped and results are limited to their permissions
Filter Validation and Incremental Results
Given the user selects a date range When the range exceeds 366 days Then Apply is disabled and a validation message instructs the user to narrow the range Given the selected filters would produce excessive bucket cardinality When the estimated bucket count exceeds the system threshold Then a warning is shown and the user must confirm to proceed or refine filters Given a query is initiated When results are being computed Then a skeleton heatmap appears within 300 ms, first aggregate values appear within 800 ms, and full aggregation completes within 2.5 seconds at P95 And a progress indicator is shown until completion Given an in-flight query is running When the user clicks Cancel Then the query is aborted and filter controls are re-enabled within 200 ms Given server-side guardrails are in place When a single query would exceed the timeout threshold Then the system executes it as segmented sub-queries and streams incremental updates without error
Export Fidelity with Cascaded Filters
Given filters are applied When the user triggers an export Then the exported file contains only meetings matching the current filters and totals match on-screen aggregates Given an export is generated When the file is downloaded Then the file includes a header with metadata: date range, business hours definition, after-hours toggle state, region, team, meeting series, owner, participant role, meeting type, request timestamp, and requesting user Given the filtered result set size is manageable When the total rows are 50,000 or fewer Then the export completes within 30 seconds and downloads immediately Given the filtered result set size is large When the total rows exceed 50,000 Then an asynchronous export job starts, the user is notified upon completion, and the downloaded file matches the requested filter state
Hotspot Thresholds & Alerts
"As a department head, I want configurable hotspot alerts so that I’m notified when after-hours burden spikes and can intervene promptly."
Description

Allow users to define thresholds (e.g., after-hours participant-hours > X or % after-hours > Y) and automatically highlight cells exceeding them. Provide optional alerts delivered via email/Slack with links back to the heatmap/drill-down and weekly summaries of emerging trends. Include default guardrails aligned to TimeTether’s fairness policies and rate-limit notifications to prevent noise. Store alert configurations per user/team with audit logs of changes.

Acceptance Criteria
Threshold Creation, Validation, and Persistence
Given a user with Thresholds:Edit permission selects a scope (region, team, or series), metric (after-hours participant-hours or % after-hours), comparator (>, >=, =), and a numeric value within allowed bounds, When they save the threshold as user-level or team-level, Then the system validates inputs, rejects invalid combinations with inline error messages, persists the configuration, and returns a success confirmation. Given an identical threshold already exists for the same scope/metric/comparator/value at the same ownership level, When a user attempts to save it again, Then the system prevents duplication and surfaces a non-blocking notice referencing the existing threshold. Given a saved threshold, When the user reopens settings or fetches via API, Then the exact saved configuration (including owner, scope, channels, enabled flag, and rate limit settings) is returned and rendered consistently in the UI.
Real-Time Hotspot Highlighting on Heatmap
Given at least one active threshold exists for a scope visible on the Ledger Heatmap, When ledger data or work-window/timezone configurations change, Then the system recalculates hotspot status and updates heatmap cell highlighting within 60 seconds. Given a cell exceeds a configured threshold, When the user hovers the cell, Then the tooltip displays metric name, comparator and threshold value, current value, delta versus prior period, and the last evaluated timestamp, and a click opens the drill-down with filters pre-applied to that cell. Given a threshold is edited or disabled, When changes are saved, Then the heatmap removes or updates highlighting for affected cells within 60 seconds.
Default Guardrails from Fairness Policy
Given a user/team has no custom thresholds, When opening the Thresholds settings, Then default thresholds are pre-populated from the Fairness Policy service and match the values returned by the policy API for the tenant, labeled as Default. Given a user chooses Reset to defaults, When confirmed, Then current custom thresholds are replaced with the latest policy defaults and the action is recorded in the audit log with before/after values and actor. Given the fairness policy is updated server-side, When a user without customizations next loads settings, Then the newly effective defaults are shown; users with custom thresholds are not overridden.
Alert Delivery via Email and Slack with Deep Links
Given alerts are enabled for a threshold and a cell breaches for the first time in the current evaluation window, When the evaluation job runs, Then an alert is sent to the selected channels (email and/or Slack) containing scope, metric, comparator and threshold value, current value, time window, and deep links to the heatmap view (with filters applied) and to the drill-down meeting list. Given a channel delivery error occurs (e.g., Slack webhook failure), When sending the alert, Then the system retries up to the configured max attempts with exponential backoff and records delivery status and last_error; on eventual failure the UI shows Delivery failed. Given a user unsubscribes from a channel or disables alerts for a threshold, When the next breach occurs, Then no notification is sent to the disabled channel while other enabled channels continue to receive alerts.
Notification Rate Limiting and De-duplication
Given multiple breaches occur for the same scope+metric+comparator within the configured cooldown window, When evaluating alerts, Then the system sends at most one alert and suppresses subsequent duplicates until the cooldown elapses, recording the suppressed count. Given a breach clears below threshold and later exceeds again after the cooldown, When evaluated, Then a new alert is sent. Given the cooldown configuration is changed at the user or team level, When saved, Then the new rate limit takes effect for subsequent evaluations and is displayed as the effective limit in the UI.
Weekly Hotspot Trend Summary
Given weekly summaries are enabled, When the weekly job executes at the user’s configured day/time and timezone, Then a summary is delivered to the selected channels containing: newly breached hotspots this week, persistent hotspots, breach counts by scope, week-over-week percent change for each hotspot, and deep links that open the heatmap/drill-down with matching filters applied. Given no hotspots breached during the week, When the summary job runs, Then no summary is sent and the run is logged as Skipped: no hotspots. Given a user opts out of weekly summaries, When the next weekly job runs, Then that user receives no summary while other users’ preferences are respected.
Configuration Storage and Audit Logging
Given a user or team admin creates, updates, or deletes an alert threshold, When the action is performed, Then the configuration is stored scoped to user or team and an immutable audit record is created capturing actor, timestamp, action, and field-level before/after values. Given audit logs are requested for a threshold, When viewed in the UI or fetched via API, Then the complete chronological history is returned and is read-only. Given cross-tenant isolation, When querying configurations or audit logs, Then users can access only records within their tenant and within their permissions (e.g., team admins can manage team thresholds; members can manage personal thresholds).
Role-based Access & Data Privacy Controls
"As a security-conscious admin, I want access controls and privacy safeguards on the heatmap so that sensitive meeting data is protected while still enabling actionable insights."
Description

Enforce RBAC so users only see aggregates and details appropriate to their scope (e.g., org-wide vs. team-level). Mask or aggregate sensitive fields when minimum cohort sizes are not met and honor regional data residency rules. Integrate with TimeTether’s existing identity and org model, apply row-level security on queries, and log access for audits. Provide admin settings to manage visibility of cost metrics versus time-based metrics.

Acceptance Criteria
Org Admin Org-Wide Heatmap Visibility & Drill-Down
Given an authenticated Org Admin user in TimeTether When they open the Ledger Heatmap and apply any region/team/series filters Then all org-wide aggregates and sensitive metrics (cost and time) render unmasked And color scales and totals reflect complete organization data Given the Org Admin clicks any heatmap cell When drilling down to meetings Then meeting-level details load for that cell including cost and time fields And export of the current view includes the same unmasked data and matches on-screen totals Given the Org Admin attempts to access an out-of-organization resource id When requesting via API or UI Then the request is denied with HTTP 403 and zero data rows returned
Team Lead Scoped Visibility & Drill-Down
Given an authenticated Team Lead who owns teams A and B When they view the Ledger Heatmap Then only data for teams A and B is included in aggregates, color scales, and totals And cells for out-of-scope teams are hidden or disabled Given the Team Lead clicks a heatmap cell for team A or B When drilling down to meetings Then meeting-level details load only for meetings belonging to teams A or B Given the Team Lead attempts to drill into a cell for an out-of-scope team When the request is made Then the UI shows an "Insufficient permissions" message and the API responds HTTP 403 with no data rows And exporting the current view includes only rows within teams A and B
Minimum Cohort Size Masking for Sensitive Metrics
Given the cohort masking threshold is 5 unique participants When a heatmap cell (any region/team/series) has fewer than 5 unique participants Then all cost-related fields display as "Masked" and drill-down is disabled for those cost fields And time-based fields remain visible only if allowed by admin policy Given the same masked cell is exported When generating CSV or using the API Then cost fields appear as "Masked" (or null) consistently and totals do not reveal masked values Given a split or pivot would allow differencing to infer a masked value When rendering aggregates Then the system also masks the complementary aggregate to prevent re-identification
Regional Data Residency Enforcement
Given an organization and/or dataset marked with EU data residency in the org model When any user views, drills down, or exports Ledger Heatmap data for EU-scoped teams Then all query execution and storage access occur in the EU region and no cross-region replication is used for the request And the audit event records dataRegion = EU Given a user located outside the EU accesses EU data within their permissions When the request is processed Then only authorized fields are returned and processing remains in-region Given a user attempts to drill across a region boundary that violates residency policy When the request is made Then the API responds HTTP 403 and no data is returned
Row-Level Security Applied to Heatmap Aggregations & Drill-Down
Given a signed-in user with role and org/team assignments from TimeTether identity When they request any heatmap aggregation or meeting drill-down Then server-side row-level security filters apply based on their role and allowed org/team ids And out-of-scope rows are excluded from aggregates and inaccessible in drill-down Given the user tampers with client-side filters to include out-of-scope team ids When the API request is received Then only in-scope rows are returned and no increase in row count or sum occurs relative to authorized scope Given an unauthorized drill-down to a specific meeting id When the request is made Then the API responds HTTP 403 and the audit log records a Deny with reason RLS
Comprehensive Audit Logging for Heatmap Access
Given any Ledger Heatmap view, drill-down, export, or access denial occurs When the action completes Then an audit event is recorded with userId, role, orgId, teamIds (if applicable), action, resource, dataRegion, result (Allow/Deny), reason (if Deny), timestamp (UTC), and requestId And sensitive metric values are not stored in the audit payload Given an Org Admin opens the audit log for a date range When filtering by action = export and result = Deny/Allow Then the matching events are returned and can be downloaded as CSV And each event links to the originating requestId for traceability
Admin Controls for Cost vs Time Metric Visibility
Given an Org Admin opens Ledger Heatmap admin settings When they toggle Cost metrics visibility Off at the org level Then all cost fields in UI, drill-down, tooltips, API, and exports display "Hidden by admin" and are excluded from calculations And color scales and totals switch to time-based metrics Given the admin sets a team-level override that is less restrictive than org-level When the policy is evaluated Then the effective policy remains the most restrictive (org-level Off prevails) Given a non-admin user When viewing settings or attempting to modify them Then the settings UI is not shown and any API attempt returns HTTP 403 Given the admin changes visibility settings When the setting is saved Then the change propagates and is effective across UI, API, and exports within 60 seconds

Threshold Alerts

Proactive Slack/Teams and email alerts at customizable spend thresholds (e.g., 50/75/90/100%), with suggested slot swaps or pauses. Keeps owners ahead of budget exhaustion and reduces last-minute chaos.

Requirements

Configurable Spend Thresholds & Routing
"As an org admin, I want to configure spend thresholds, channels, and quiet hours per meeting series so that alerts reach the right people at the right time without noise."
Description

Provides an admin UI and API to define budgets and alert thresholds per meeting series or team (defaults at 50/75/90/100% with support for custom levels). Lets owners select delivery channels (Slack, Microsoft Teams, email), recipients, escalation paths, quiet hours, and do-not-disturb windows. Integrates with TimeTether’s org, team, and meeting-series models; maps alerts to meeting owners and alternates. Includes OAuth-based Slack/Teams app installation, secure secret management, and per-channel validation. Offers preview/testing mode to simulate alerts before enabling. Ensures tenant-level and series-level overrides, inheritance, and conflict resolution.

Acceptance Criteria
Admin sets custom spend thresholds for a meeting series
Given a tenant with default thresholds set to 50/75/90/100% When an admin creates a meeting series without custom thresholds Then the series inherits the tenant defaults and they are visible in the series settings Given a meeting series When an admin adds custom thresholds of 40/60/80/95% in ascending order Then the system validates and saves them and uses them for alerting Given a meeting series When an admin attempts to save thresholds that are not ascending or outside 1–100 Then the system rejects the change with a validation error explaining the failed rules Given a meeting series with custom thresholds When the admin resets to default Then the thresholds revert to the tenant defaults
Configure multi-channel alert routing with validation and fallback
Given Slack is not installed for the tenant When the admin selects Slack as a delivery channel Then the UI prompts to install via OAuth and blocks save until installation succeeds Given Slack is installed and a channel or user is selected When the admin saves routing Then a test validation call confirms the destination is reachable and the configuration is marked valid Given an alert is triggered and the primary channel fails delivery Then the system retries per policy and falls back to the next configured channel and records the path in the audit log Given email is chosen as a channel When the recipient address is invalid Then the system blocks save with an actionable validation message
Escalation on unacknowledged threshold alerts
Given an alert recipient list with an owner and an alternate When a 90% threshold alert is delivered Then the message includes an acknowledge action and the alert status is Awaiting Ack Given the alert remains unacknowledged for the configured escalation timeout (e.g., 30 minutes) When the timeout elapses Then the system escalates to the next recipient tier and updates the alert status and audit log Given an alert has been acknowledged by any recipient When escalation would otherwise occur Then escalation is suppressed and no further notifications are sent Given multiple thresholds trigger in sequence (e.g., 75% then 90%) When the first alert is acknowledged Then subsequent alerts still dispatch according to configuration
Quiet hours and do-not-disturb enforcement
Given quiet hours are set for 20:00–07:00 local time for a recipient When a threshold is crossed during quiet hours Then the system queues the alert and delivers it at the end of quiet hours unless marked as override Given do-not-disturb is enabled for weekends When a threshold is crossed on Saturday Then no alert is sent until DND ends and the queued alert is delivered with original timestamp and reason Given an alert is marked as override by policy (e.g., 100%) When it occurs during quiet hours Then it bypasses quiet hours but respects channel-level DND if applicable Given recipients are in different timezones When sending alerts Then each recipient’s quiet hours and DND windows are evaluated individually
Policy inheritance and conflict resolution across tenant, team, and series
Given tenant-level defaults and a team-level override exist When a new series is created under that team Then the series inherits the team override unless a series-specific override is set Given conflicting settings (e.g., tenant quiet hours vs series override) When effective policy is computed Then series-level overrides take precedence over team, and team over tenant, and the effective policy is displayed in the UI Given an admin tests effective policy for a specific series When viewing its configuration Then the UI shows the origin of each effective value (tenant/team/series) and any conflicts resolved Given tenant-level defaults change When computing alerts for series without local overrides Then the effective policy updates for new alerts while historical audit records remain unchanged
Slack and Teams OAuth installation and secret management
Given an admin initiates Slack app installation When OAuth completes Then the system stores tokens encrypted, scopes match required set, and a validation ping confirms connectivity Given a token is rotated in Slack or Teams When the system detects auth failure Then it marks the channel configuration invalid and prompts admin to re-authorize without sending alerts Given a deauthorization webhook is received When processed Then all related channel routes are disabled and owners are notified via email fallback Given a secrets store outage occurs When attempting to send alerts Then no secrets are logged, alerts fail closed with retriable errors, and an operational alert is raised to administrators
Preview mode to simulate alerts without side effects
Given an admin enables Preview mode for a series When triggering a test at 75% Then only test messages are sent to the configured destinations tagged as PREVIEW and no budget state is mutated Given Preview mode is active When viewing the audit log Then preview events are clearly labeled and excluded from SLA metrics Given Preview mode is disabled When sending real alerts Then messages are sent without the PREVIEW tag and all routing, escalation, and quiet hours rules apply Given an admin lacks permission to send previews at tenant scope When attempting to run a preview Then the action is blocked with a 403 and guidance on required roles
Budget Utilization & Forecasting Engine
"As a meeting owner, I want accurate current and projected budget utilization so that I can get ahead of overruns before they happen."
Description

Calculates accrued and projected budget spend for each meeting series/team by ingesting scheduled and historical occurrences, durations, cancellations, and changes. Supports budgets defined in hours or credits, with configurable reset cycles (weekly/monthly/quarterly) and proration for partial cycles. Forecasts threshold crossings based on future schedule and rotation, taking into account time zones, holidays, and work windows. Produces real-time utilization metrics and predicted run-out dates via an internal service with event-driven updates. Exposes clear rules for what counts as spend (booked vs. completed time) and APIs for retrieval by alerts and reporting components.

Acceptance Criteria
Accrued Spend Calculation: Booked vs Completed (Hours and Credits)
Given a meeting series with budgetType='hours', cycle='monthly', spendCountingMode='completed', and occurrences: 3 completed x 60 min, 1 canceled When the engine calculates accrued spend for the current cycle Then accruedSpend=3.0 hours and canceled occurrences contribute 0.0 hours Given the same series with spendCountingMode='booked' and occurrences: 2 completed x 60 min, 1 upcoming x 30 min, 1 canceled When the engine calculates accrued spend Then accruedSpend=2.5 hours and canceled occurrences contribute 0.0 hours Given a meeting series with budgetType='credits', creditRate=2.0 credits per hour, cycle='monthly', spendCountingMode='completed', and occurrences: 45 min completed, 15 min completed, 30 min canceled When the engine calculates accrued spend Then accruedSpend=3.0 credits and canceled occurrences contribute 0.0 credits
Budget Reset Cycle & Proration for Partial Cycles
Given a monthly budget of 100.0 hours with cycleStart=2025-09-01T00:00:00Z, onboardingDate=2025-09-10, prorationEnabled=true, monthLength=30 days When the engine computes the effective budget for the first partial cycle (2025-09-10..2025-09-30) Then effectiveCycleBudget=100.0*(21/30)=70.0 hours and next cycle (starting 2025-10-01T00:00:00Z) resets to 100.0 hours Given a quarterly budget of 300.0 hours with cycle Q3 2025 (2025-07-01T00:00:00Z..2025-09-30T23:59:59Z), onboardingDate=2025-08-16, prorationEnabled=true, quarterLength=92 days When the engine computes the effective budget for the first partial cycle (2025-08-16..2025-09-30) Then effectiveCycleBudget=300.0*(46/92)=150.0 hours and the next full cycle resets to 300.0 hours Given a weekly budget of 40.0 credits with ISO week cycle starting Monday 00:00:00Z, onboardingDate=Wednesday, prorationEnabled=true When the engine computes the first partial week budget Then effectiveCycleBudget=40.0*(5/7)=28.57 credits rounded to 2 decimals
Forecast Threshold Crossings with Time Zones, Holidays, and Work Windows
Given budgetType='hours', cycle='monthly', budgetAmount=100.0, accruedSpend=60.0 hours, and future scheduled occurrences: - A (2025-09-05T15:00Z, 5h, within work windows) - B (2025-09-07T15:00Z, 8h, falls on company holiday for series region) - C (2025-09-10T15:00Z, 7h, outside required participants' work windows) - D (2025-09-12T15:00Z, 10h, within work windows) - E (2025-09-18T15:00Z, 15h, within work windows) - F (2025-09-25T15:00Z, 12h, within work windows) When the engine forecasts threshold crossings considering time zones, holidays, and work windows Then excludedOccurrences=[B,C] and includedOccurrences=[A,D,E,F] And thresholdCrossings return: - 75% (75.0h) at occurrence D start time - 90% (90.0h) at occurrence E start time - 100% (100.0h) at occurrence F start time And runOutDate equals occurrence F end time
Event-Driven Updates and Recalculation SLA
Given spendCountingMode='booked' and an upcoming booked 60-min occurrence counted in accrued When a cancellation event with that occurrenceId is received by the engine Then accruedSpend decreases by 1.0 hour within 10 seconds and forecasted thresholds/runOutDate are recalculated and available via API within 10 seconds and the utilization version is incremented by 1 Given spendCountingMode='completed' and the same cancellation event for a not-yet-completed occurrence When the engine processes the event Then accruedSpend remains unchanged and only projections/thresholds are updated within 10 seconds
Utilization and Forecast Retrieval API Contract
Given the internal endpoint GET /engine/v1/series/{seriesId}/utilization?cycle=current is called with seriesId='abc123' When the engine returns the response Then HTTP 200 and JSON body include: seriesId (string), cycleStart (ISO-8601 UTC), cycleEnd (ISO-8601 UTC), budgetType ('hours'|'credits'), budgetAmount (number), effectiveCycleBudget (number), accrued (number), projected (number), thresholdCrossings (array of {threshold:number,dateTime:ISO-8601}), runOutDate (ISO-8601|null), lastUpdatedAt (ISO-8601 UTC), version (integer) And P95 latency <= 500 ms for response payload <= 50 KB and data staleness <= 5 seconds from last processed event And invalid or unknown seriesId returns HTTP 404 with error code 'SERIES_NOT_FOUND' Given the internal endpoint GET /engine/v1/team/{teamId}/utilization?cycle=current is called for aggregation When meetings span multiple series within the team Then the response aggregates budgets and spend per team with the same field contract and includes seriesBreakdown[]
Cancellations, Reschedules, and Duration Changes Handling
Given an occurrence with occurrenceId='m1' initially scheduled for 30 min (booked) When it is rescheduled to 45 min before completion Then the engine counts 0.5 hours under spendCountingMode='booked' and 0.75 hours under spendCountingMode='completed' once completed, and never double-counts 'm1' Given an occurrence with occurrenceId='m2' completed with actual duration 40 min and scheduled duration 30 min When spendCountingMode='completed' Then accrued uses 0.67 hours (rounded to 2 decimals) and ignores scheduled duration Given duplicate ingestion events for the same occurrenceId with different updatedAt timestamps When the engine consolidates occurrences Then only the latest event by updatedAt is counted and time zones are normalized to UTC for all stored timestamps
Alert Triggering & Multichannel Delivery
"As a meeting owner, I want timely alerts delivered via Slack, Teams, or email so that I can take action without checking another dashboard."
Description

Implements a rules engine that evaluates utilization and forecast signals to trigger alerts when thresholds are reached or expected to be crossed within a configurable lookahead window. Provides deduplication, cooldown periods, and grouping across overlapping rules. Delivers interactive Slack/Teams messages and emails with localized times, deep links, and context on budget state. Includes templating, retry with exponential backoff, fallback channels, idempotency keys, rate limiting, and observability (metrics, logs, traces). Ensures content security, transport encryption, and per-tenant branding.

Acceptance Criteria
Lookahead Triggering at Forecasted Threshold Crossing
Given tenant T has thresholds at 50%, 75%, 90%, and 100% with a 7-day lookahead and minimum forecast confidence of 0.7 And current utilization is 68% with projected crossing of 75% in 3 days at 2025-09-06T10:00:00Z with confidence 0.82 When the rules engine evaluates utilization and forecasts on its scheduled run Then a forecast alert is created for threshold 75% with ETA 2025-09-06T10:00:00Z and confidence 0.82 And the alert payload includes current utilization, burn rate, forecast ETA, threshold, and rule identifiers And no alert is created if the projected crossing is outside the 7-day window or confidence < 0.7 And alert priority is set to High if ETA <= 24 hours, Medium if 24–96 hours, Low otherwise
Real-Time Threshold Reached with Deduplication, Cooldown, and Grouping
Given tenant T has thresholds 75% and 90% with a grouping window of 30 minutes and cooldown of 24 hours And idempotency keys are computed as hash(tenantId, ruleId, threshold, periodStart, channel) When utilization crosses 75% at 10:00 and emits multiple events until 10:20 Then only one 75% alert is sent within the 30-minute grouping window And if utilization crosses 90% at 10:25 (within grouping window), a single consolidated alert references both 75% and 90% thresholds And subsequent events within the 24-hour cooldown for the same thresholds do not create new alerts And duplicate processing attempts with the same idempotency key do not produce additional sends across any channel
Multichannel Delivery with Fallback and Exponential Backoff
Given delivery preferences are Slack, then Teams, then Email as fallback And the retry policy is exponential backoff [1m, 2m, 4m] with maxAttempts=4 per channel And per-tenant, per-channel rate limit is 5 alerts per minute When Slack returns 5xx and Teams returns 429 with a Retry-After header Then Slack retries follow the backoff schedule and stop after 4 attempts And Teams retries honor Retry-After and backoff, without exceeding 4 attempts And upon exhausting Slack and Teams attempts, an Email notification is sent via SMTP And delivery attempts, outcomes, and latencies are recorded for each channel And rate limits are enforced so that no more than 5 alerts/minute/tenant/channel are delivered
Interactive Slack/Teams Messages with Suggested Actions and Deep Links
Given an alert is sent to Slack or Teams using the interactive message template When the message is rendered for a recipient Then it includes localized times, current and forecast utilization, remaining budget, and actions: Propose Slot Swap, Pause Scheduling, Open Budget And all action buttons post back to signed endpoints (HMAC-validated) including tenantId, userId, and alertId And deep links contain short-lived (<=15 minutes) signed tokens and route to tenant-scoped pages And if interactivity is unsupported, a plain-text fallback with equivalent hyperlinks is delivered And messages with missing template variables fail validation and are not sent
Email Notifications with Localization and Tenant Branding
Given a recipient has locale fr-FR and timezone Europe/Paris and the tenant has branding assets configured When an email alert is generated Then subject and body are localized to fr-FR, dates/times are shown in Europe/Paris, and numbers use fr-FR formatting And the email applies tenant logo and accent color and includes budget state, threshold hit/ETA, and next-step links And delivery uses TLS 1.2+ (or STARTTLS) with DKIM/SPF passing; if TLS unavailable, the send is deferred and retried And an alert preferences link scoped to the tenant is included
Idempotency Keys and Exactly-Once Send Semantics
Given concurrent workers and at-least-once event delivery from the rules engine And idempotency keys are hash(tenantId, ruleId, threshold, periodStart, channel) When multiple send requests with the same key are processed across workers or after retries Then only one message per channel is delivered and subsequent attempts are logged as duplicates without side effects And idempotency records persist for at least 48 hours and survive process restarts And key collisions across tenants or periods do not occur
Observability, Security, and Content Sanitization
Given alert lifecycle events (triggered, consolidated, sent, failed, retried, delivered) When the system operates under normal and failure conditions Then metrics are emitted: alerts_triggered, alerts_consolidated, alerts_sent, alerts_failed, retries, by channel and tenant, with p95 time-to-first-delivery < 10s And distributed traces capture tenantId, ruleId, threshold, channel, attempt, dedupeKey, with configurable sampling And logs exclude message bodies and secrets; any rendered content is sanitized (HTML escaped; Slack/Teams markdown allowlist) And all external calls (Slack, Teams, SMTP) use TLS 1.2+; deep-link tokens are signed, audience-bound, and expire <= 15 minutes
Suggestion Engine for Swaps/Pauses
"As a meeting owner, I want actionable suggestions like swaps or pauses that respect team constraints so that I can reduce spend with minimal disruption."
Description

Generates actionable recommendations when utilization nears or exceeds thresholds, including skipping the next occurrence, shortening duration, swapping to lower-cost slots, or pausing until the next budget cycle. Respects TimeTether’s fairness rotation, participant time zones, preferred work windows, and hard constraints (e.g., no Fridays, blackout dates). Scores and ranks options by impact on budget, fairness, and disruption, and explains trade-offs in plain language. Outputs machine-readable payloads and one-click action links for alert messages.

Acceptance Criteria
Threshold Alert Produces Ranked Suggestions
Given budget utilization for a meeting series crosses a configured threshold, When the alert is generated, Then the engine returns 2-5 distinct suggestions covering at least two action types among {skip_next, shorten_duration, swap_slot, pause_until_next_cycle}. And each suggestion includes numeric scores for budget_impact, fairness, disruption, and a composite_score. And suggestions are ordered by composite_score descending with no ties in the top 3. And the plain-language summary is <= 300 characters and mentions the primary trade-off. And p95 generation latency is <= 2 seconds for series with <= 200 future occurrences.
Constraints and Fairness Are Respected
Given series hard constraints (e.g., no Fridays, blackout dates), participant preferred work windows, and fairness rotation state, When generating suggestions, Then no suggestion violates hard constraints, blackout dates, or preferred work windows. And no suggestion schedules any participant outside their local working hours. And fairness rotation order is preserved unless a fairness_delta is included; suggestions with fairness_delta above configured max are excluded.
Correct Time Zone Handling and DST in Swaps
Given participants span multiple time zones with upcoming DST transitions, When producing swap_slot suggestions, Then proposed start/end times are returned in ISO 8601 with explicit UTC offsets and correct local times for each participant. And no participant is moved outside their configured work window after DST adjustments. And cost estimates use the correct local time-based pricing buckets post-DST.
Machine-Readable Payload and One-Click Actions
Given suggestions are generated for an alert, When building the outbound payloads, Then each suggestion includes JSON fields: action_type, series_id, occurrence_id(s), proposed_start, proposed_end, timezone, duration_minutes, effective_date, expected_budget_delta, fairness_delta, disruption_score, scores, reason_codes, idempotency_key, signature, link_url. And link_url is signed, single-use, expires in 24 hours, and returns HTTP 200 on success, 409 on duplicate, 401 on expired/invalid. And invoking link_url applies the change and responds with the finalized schedule and status=applied in the same schema.
Explainability of Trade-offs
Given any generated suggestion, When producing the explanation, Then the text is plain language (<= grade 9 reading level), <= 300 characters, and states the main pro and con. And it references expected_budget_delta and fairness_delta and any relevant constraints. And explanations contain no internal IDs or jargon tokens.
Budget Impact Projection Accuracy
Given the current utilization and pricing rules, When computing expected_budget_delta and projected utilization for each suggestion, Then backtest results across at least 100 sampled series show projection error within +/-1% or +/-$5 (whichever is greater). And the payload includes currency and fiscal period used for the projection.
Graceful Handling When No Valid Suggestion Exists
Given constraints eliminate all valid action types, When generating suggestions, Then the engine returns an empty suggestions array with reason_codes including at least one of {no_valid_slot, hard_constraints_block, fairness_penalty_too_high}. And the alert omits one-click action links and includes a non-actionable info message. And the event is logged with series_id, threshold, and timestamp for audit.
One-Click Apply with Calendar Sync & Rollback
"As a meeting owner, I want to apply suggested changes with one click and revert if needed so that I can act quickly and safely."
Description

Executes suggested changes from interactive alerts via secure, signed links or message buttons. Performs authorization checks, applies swaps/pauses/shortens across Google Calendar and Microsoft 365 using organizer credentials, sends updated invites, and notifies participants. Updates fairness rotation, utilization metrics, and forecasts post-change. Ensures atomic, idempotent operations with optimistic concurrency and provides a time-bound undo option with automatic rollback if errors occur. Includes guardrails to avoid after-hours regressions.

Acceptance Criteria
One-Click Apply from Interactive Alert (Signed Link/Button)
Given a Slack/Teams/email threshold alert with a signed action link or button that expires in 15 minutes, When the owner clicks it before expiry and the signature validates, Then the system applies the suggested change without additional prompts and posts a success confirmation in the original thread or email within 5 seconds. Given the same alert, When the link is expired, tampered, or the signature is invalid, Then the system returns HTTP 403, posts a failure notice without sensitive details, and makes no changes. Given the alert, When the link/button is clicked by a user who is not the intended owner or not in the tenant, Then the system denies the action, logs an audit entry, and performs no calendar or metric updates.
Organizer Authorization Enforcement
Given a suggested change affecting events organized by a specific account, When one-click apply is invoked, Then the system uses the organizer’s OAuth credentials; if missing or expired, it attempts a single token refresh; on failure, it aborts with a clear message and makes zero changes. Given the invoker lacks organizer rights over any target event, When apply is attempted, Then the operation is aborted with an error identifying the blocked calendars (non-sensitive) and no updates are committed. Given a multi-tenant environment, When apply is invoked, Then all write operations verify tenant ID matches the invoker’s tenant; any mismatch aborts with no side effects.
Cross-Provider Application and Invite Updates
Given suggested swaps/pauses/shortens spanning Google Calendar and Microsoft 365, When one-click apply succeeds, Then the corresponding events are created/updated/canceled in each provider with correct times, attendees, and descriptions within 60 seconds. Given attendee lists for affected events, When changes are persisted, Then updated invitations or cancellations are sent to all participants with correct ICS details and organizer identity, and a summary with affected meeting IDs is posted to the originating thread or email. Given provider rate limits are encountered, When apply executes, Then the system retries with exponential backoff up to 3 times; persistent failure triggers a full rollback and a failure notice.
Atomic, Idempotent Execution with Optimistic Concurrency
Given a batch of proposed changes, When one-click apply executes, Then the operation is atomic: either all event updates commit or none commit. Given the action link/button is clicked multiple times or the request is retried, When the same idempotency key is received, Then only the first successful execution applies changes; subsequent attempts return the original result with no additional side effects. Given any target event has changed since the alert was generated (version/ETag mismatch), When apply is attempted, Then optimistic concurrency detects the conflict, aborts the operation, and posts a refresh-required message with a new suggestion link.
Undo Window and Automatic Rollback on Error
Given a successful apply, When the owner selects Undo within 15 minutes, Then all changes are reverted across providers, original invites are restored, fairness/metrics are reverted, and a confirmation is posted to the originating thread or email. Given a partial failure occurs during apply at any provider, When error handling triggers, Then the system performs an automatic rollback to the pre-change state, marks the action as failed, and records a linked audit trail for both attempts. Given the undo window has expired, When Undo is requested, Then the request is declined with an expiry message and no changes are made.
After-Hours Regression Guardrails
Given participant working windows and the current baseline schedule, When suggested changes are applied, Then the resulting schedule over the next 4 weeks introduces no increase in after-hours meetings for any participant compared to baseline. Given a proposed change would create an after-hours meeting for any participant, When apply is attempted, Then the system selects an alternative within working windows or aborts with a guardrail explanation; no after-hours regression is committed. Given timezone differences and fairness constraints, When a swap is applied, Then the maximum after-hours count per participant does not increase relative to the pre-change state over the next 4 weeks.
Post-Change Fairness Rotation and Forecast Update
Given a successful apply, When the operation completes, Then fairness rotation positions are recalculated and persisted, utilization metrics are recomputed, and scheduling forecasts are updated within 60 seconds. Given dashboards and APIs consume these metrics, When queried within 60 seconds of completion, Then they reflect the new state including updated rotation positions, utilization percentages, and forecasted availability. Given budget or utilization forecasts are impacted by pauses/shortens, When apply completes, Then projected exhaustion dates/run-rates are recalculated and posted to the originating alert thread or email.
Audit Trail, Analytics & Governance
"As a compliance-conscious admin, I want an auditable record of alerts and actions so that we can report outcomes and meet governance requirements."
Description

Captures immutable logs of alerts, thresholds crossed, user actions taken, and outcomes on utilization. Provides dashboards and exports showing budget trajectory, alert volume, prevented overruns, and action effectiveness by team/series. Implements RBAC for viewing and managing alerts, data retention policies, PII minimization/redaction, and tenant-scoped access. Emits webhooks for downstream BI and produces weekly summaries to stakeholders.

Acceptance Criteria
Immutable Audit Log for Threshold Alerts and Actions
- Given an alert is generated at a threshold (50/75/90/100%) for a series, When the system emits the alert, Then an audit entry is persisted with fields: event_id, tenant_id, series_id, threshold_pct, channels, alert_time_utc (ISO 8601), actor=system, hash, previous_hash, status=emitted. - Given a user initiates an action from an alert (slot_swap|pause|resume|ignore), When the action is executed, Then an audit entry is persisted with fields: action_type, action_id, actor_user_id, actor_role, request_time_utc, result_status (success|failure), correlation_id=event_id. - Given any audit entry exists, When integrity verification runs, Then the hash chain validates for 100% of sampled entries and any mutation causes verification to fail with code INTEGRITY_FAIL and is itself logged. - Given duplicate alert processing, When idempotency is applied, Then exactly one audit entry exists per event_id.
Analytics Dashboards for Budget Trajectory and Alert Effectiveness
- Given a user with role Manager or higher, When they open Analytics, Then charts for budget trajectory, alert volume, prevented overruns, and action effectiveness by team and series are visible for selectable ranges (last 7/30/90 days and custom). - Given new alerts or actions occur, When data pipelines run, Then dashboards reflect changes within 5 minutes (p95) and metric totals reconcile with exports within 0.5%. - Given filters (team, series, threshold, channel), When applied, Then visuals update within 2 seconds (p95) and only in-scope data is shown. - Given a CSV export request up to 100k rows, When generated, Then it completes within 60 seconds (p95) and includes metric definitions in the header.
Role-Based Access Control and Tenant Scoping
- Given roles Owner, Admin, Manager, Viewer, When accessing features, Then permissions are enforced: Viewer=read-only analytics; Manager=view alerts/logs and act on assigned teams; Admin=manage thresholds, webhooks, retention; Owner=all tenant settings. - Given a user belongs to tenant A, When they request any resource, Then only tenant A data is returned; cross-tenant access returns 403 and is logged in the audit trail. - Given an action requiring elevated permissions (e.g., retention change), When attempted without sufficient role, Then the UI control is disabled and the API returns 403 with error code RBAC_001.
Data Retention and PII Minimization/Redaction
- Given default retention, When policies are applied, Then raw alert payloads are retained 90 days, audit logs 365 days, and analytics aggregates 730 days, configurable per tenant within documented min/max. - Given PII fields (user_email, full_name, calendar_event_title), When stored or exported, Then they are omitted or irreversibly redacted: emails/tokenized, names/initialized, titles/hashed with salt; user_id permitted; no plaintext PII leaves the system. - Given nightly retention jobs, When records exceed policy windows, Then they are purged, a summary audit entry is created, and queries no longer return purged data. - Given an export or webhook, When pii_redaction is required, Then payloads include masked fields and a pii_redaction=true flag.
Webhooks for Downstream BI
- Given a subscribed endpoint with secret, When events occur (threshold_crossed, alert_emitted, action_taken, outcome_recorded), Then a JSON payload is delivered with schema_version, event_id, tenant_id, series_id, occurred_at, and an HMAC-SHA256 signature header. - Given delivery failures (HTTP 5xx/timeout), When sending, Then retries use exponential backoff with jitter up to 8 attempts over 24 hours; any 2xx stops retries; persistent 4xx (3x) disables the subscription and notifies Admins. - Given idempotency and ordering requirements, When consumers receive events, Then event_id ensures de-duplication and ordering is preserved per series_id. - Given schema evolution, When a new version is released, Then prior versions remain available for 90 days and OpenAPI docs and samples are published.
Weekly Stakeholder Summaries
- Given weekly cadence at Monday 09:00 tenant-local, When summaries run, Then Owners/Admins receive Slack/Email containing: spend vs plan, alert volume, top 5 at-risk series, prevented overruns, action effectiveness, and recommendations. - Given user notification preferences, When a recipient unsubscribes or changes channel, Then the next cycle respects the preference; emails include a working unsubscribe link. - Given reporting window, When the summary is generated, Then it covers prior Monday–Sunday and reconciles with Analytics within 0.5% variance. - Given delivery issues, When email/Slack fails, Then two retries occur and failures are recorded in the audit trail.
Audit Log Search and Export
- Given filters (date range, team, series_id, threshold_pct, action_type, actor, outcome), When applied to the audit log, Then results return within 2 seconds (p95) for up to 1M records via pagination. - Given an export request (CSV or JSON), When initiated, Then the export reflects current filters, excludes redacted PII, includes a signed tamper-evidence manifest, and remains downloadable for 7 days. - Given a specific event_id lookup, When queried via API, Then the event and its correlated chain (alert→action→outcome) are returned within 200 ms (p95).

Justified Overrides

When budgets are exhausted, require a brief, logged justification to proceed, with approver workflows and auto-expiring exceptions. Preserves agility for critical events while maintaining accountability and auditability.

Requirements

Budget Threshold Detection Engine
"As a scheduling owner, I want TimeTether to detect when a meeting would exceed someone’s budget so that I don’t inadvertently schedule unfair or out-of-policy meetings."
Description

Automatically tracks and evaluates per-user and per-team scheduling budgets (e.g., after-hours minutes, cross-timezone burden, override quotas) in real time during slot search and booking. When a proposed meeting exceeds configured thresholds, the engine flags the condition, blocks auto-booking, and initiates the Justified Overrides flow. Integrates with TimeTether’s fairness rotation and timezone analysis to compute impact scores, supports rolling windows (weekly/monthly), and respects holidays and custom work windows. Provides deterministic outcomes and clear reasons for triggering an override requirement.

Acceptance Criteria
Real-Time Threshold Flagging During Slot Search
Given a slot search request with participants and configured per-user and per-team budgets for after-hours minutes, cross-timezone burden, and override quotas within active rolling windows When the engine evaluates each candidate time slot Then for any slot where any participant’s projected metrics would exceed their remaining budget in the relevant window, the engine must set override_required=true for that slot, attach reason_codes for each triggered budget including: budget_type, entity_scope (user|team), entity_id, window_type (weekly|monthly), projected_value, limit, remaining_before, delta, and prevent auto-booking And for slots that do not exceed any configured thresholds, the engine must set override_required=false and include per-entity projected metrics And the decision (override_required and reason_codes) must be computed before slot results are returned to the caller
Rolling Window Budget Computation With Holidays And Custom Work Windows
Rule 1: Weekly rolling window = trailing 7×24 hours from evaluation timestamp; Monthly rolling window = trailing 30×24 hours from evaluation timestamp Rule 2: After-hours minutes per user are computed in the user’s local timezone as the minutes of the proposed meeting that fall outside that user’s configured work windows (supporting overnight windows) Rule 3: Minutes that fall on a user’s holiday (per assigned holiday calendar) are treated as non-working and count toward after-hours minutes for that user Rule 4: Cross-timezone burden is computed as the minutes of the proposed meeting that fall outside the intersection of all participants’ work windows; per-user burden attribution is recorded Rule 5: Daylight Saving Time transitions are correctly handled using each user’s timezone database so that minutes are not double-counted or missed Rule 6: All computations use ISO-8601 timestamps with explicit offsets and are performed deterministically
Per-User And Per-Team Budget Enforcement And Reason Codes
Given budgets are configured at both user and team scopes for the relevant metrics When the engine evaluates a proposed slot Then if either the per-user or per-team budget would be exceeded by the projected meeting, the engine must block auto-booking, set override_required=true, and include separate reason_codes entries for each triggered scope And the reason payload must include for each scope: scope (user|team), scope_id, budget_type, window_type, current_used, projected_used, limit, remaining_before, delta And if both scopes remain within limits, the engine must include remaining budgets for each scope in the slot’s metadata and allow auto-booking
Fairness Rotation Integration And All-Slots-Exceed Case
Given a fairness-rotation policy with weights and the engine’s impact score definition (impact_score = weighted sum of per-user after-hours minutes and cross-timezone burden for the slot) When generating candidate slots within the search horizon Then the engine must rank candidates by lowest incremental impact_score while honoring rotation weights and exclude any slot with override_required=true from the auto-bookable set And if at least one slot exists with override_required=false, the engine must return only those as auto-bookable (ordered by impact_score and rotation) And if no such slot exists, the engine must mark all candidates override_required=true and return a recommended list of the top 3 slots with the lowest impact_score, each with reason_codes explaining which budgets require override
Override Flow Initiation And Auto-Expiring Exceptions
Given a slot is marked override_required=true When the requester provides a justification note and submits Then the engine must create an override_request with fields: request_id, slot_id, requester_id, justification, triggered_reason_codes, created_at, and set status=pending_approval while blocking auto-booking When an approver approves with an expiry policy (until event end or explicit TTL) and optional scoped limits (e.g., max extra after-hours minutes) Then the engine must create an exception record scoped to the specified entity (user/team) and budget_type, enforce the limits, and allow booking that slot if within the exception bounds before expiry And after the exception expires or limits are consumed, subsequent evaluations must revert to standard budget enforcement And all state changes (request, approval/denial, booking) must be appended to an immutable audit log with: actor_id, action, timestamp, request_id/exception_id, and diff of affected budgets
Deterministic Outcomes And Idempotent Decisions
Given identical inputs (participants, time ranges, budgets, work windows, holidays, fairness configuration) and the same evaluation timestamp When the engine evaluates a specific slot more than once or in different candidate orders Then the override_required flag, impact_score, and reason_codes must be identical across runs And the engine must emit a stable decision_id derived from the canonicalized inputs and slot UTC timestamp so that replays are idempotent And no randomized tie-breaking is used; ties are resolved via a documented, stable order (e.g., ascending slot UTC, then participant_id)
Justification Prompt and Capture
"As a meeting organizer, I want to provide a concise, logged reason for an exception so that I can proceed when it’s critical while preserving accountability."
Description

Presents a lightweight, inline modal when budgets are exceeded, requiring a brief justification to proceed. Captures structured fields (reason category, free-text rationale with character limit, urgency level, affected participants), auto-populates meeting context (time, attendees, timezones, burden score), and validates inputs against policy. Stores entries with immutable timestamps and links to the meeting record. Supports keyboard-first flow and accessibility standards, minimizing friction while ensuring accountability.

Acceptance Criteria
Modal Trigger on Budget Exceedance
Given a scheduler attempts to finalize a meeting that exceeds the team's monthly scheduling budget, When they initiate scheduling (e.g., click "Send Invites"), Then an inline justification modal appears within 500 ms, blocks progression, and places focus on the first input. Given the modal is displayed, When the user clicks outside the modal or presses Esc, Then the modal closes and the scheduling action is cancelled with no justification record created. Given budgets are not exceeded, When the user schedules, Then the justification modal is not displayed and scheduling proceeds normally.
Structured Field Capture and Validation
Given the modal is rendered, Then it displays required fields: Reason Category (dropdown), Rationale (textarea with live character counter), Urgency (radio group), and Affected Participants (multi-select from attendee list). Given the user submits, When Reason Category is unset, Then submission is blocked and an inline error is shown and announced to screen readers. Given the user submits, When the trimmed Rationale length is < 20 or > 280 characters, Then submission is blocked and the character counter shows the exact deficit or excess. Given at least one participant is outside their work window for the proposed time, When Urgency is Low or Medium, Then submission is blocked with a message requiring High or Critical urgency. Given Affected Participants is empty, When submitting, Then submission is blocked with an inline error and the multi-select gains focus. Given Reason Category = "Other", When submitting, Then Rationale must be at least 40 characters; otherwise submission is blocked.
Auto-Populated Meeting Context Accuracy
Given the modal opens, Then Meeting Time (ISO 8601 UTC and organizer local time), Attendee list with names and timezones, and Burden Score are auto-populated as read-only fields. Given the organizer updates time or attendees before triggering the modal, When the modal opens, Then the context reflects the latest draft values exactly. Given the modal opens, When the context is programmatically compared to the current meeting draft, Then values match exactly, including timezone abbreviations and burden score version tag.
Keyboard-First and Accessibility Compliance
Given the modal is open, When navigating with Tab/Shift+Tab, Then focus order follows visual order, is trapped within the modal, and is returned to the triggering control on close. Given all required fields are valid, When pressing Enter on the primary action, Then the submission succeeds without mouse interaction. Given an input error occurs, When the error is rendered, Then focus moves to the first error and the message is announced via ARIA live with field-specific guidance. Given the modal UI, Then all interactive elements have accessible names, meet a 4.5:1 contrast ratio, and show a visible focus indicator, satisfying WCAG 2.1 AA.
Immutable Storage and Meeting Linkage
Given a valid submission, When persisted, Then the system writes a server-generated UTC timestamp, userId, meetingId, approverWorkflowId (nullable), and a SHA-256 hash of the captured payload. Given a stored justification, When any attempt is made to modify captured fields, Then the system rejects the change and appends an audit log entry capturing who attempted the change and when. Given a stored justification, When queried by meetingId, Then the record is returned within 200 ms (p95) and includes a direct link to the meeting record. Given an approval or rejection event occurs, When recorded, Then it is appended as a new immutable audit event without altering the original justification payload.
Policy-Aware Submission and Error Messaging
Given organization policy rules are loaded, When the user submits, Then client-side validation enforces rule preconditions and displays field-specific messages (no generic "invalid"). Given a policy requires approval for >50% budget overage, When such a submission is captured, Then the UI marks it as "Requires Approval" and the record is routed to the approver workflow after successful capture. Given a temporary exception policy with auto-expiry (e.g., 7 days), When submission includes an exception, Then the stored record contains an expiry timestamp and the UI indicates the expiry date. Given a transient network failure during submission, When retry logic executes, Then the user is prompted to retry and, upon success, exactly one justification record exists (no duplicates).
Approval Routing and Escalation Workflow
"As a team lead approver, I want clear, one-click approval requests with context so that I can quickly approve critical exceptions and prevent unnecessary after-hours meetings."
Description

Routes justification submissions to the appropriate approver(s) based on configurable rules (org hierarchy, team ownership, cost center, severity). Delivers one-click approve/deny via web, email, and mobile notifications, with SLAs, reminders, and fallback delegates. Supports multi-step approvals, conditional auto-approval for predefined scenarios, and automatic proposal of compliant alternatives if denied. Blocks invite dispatch until approval and records outcomes with full provenance (who, when, from where).

Acceptance Criteria
Route to Primary Approver by Cost Center and Severity
- Given a justification submission includes teamId, costCenter='CC-123', severity='High', and routing ruleset R1 (v1.0) maps CC-123+High to approver 'A123' When the requester submits the justification Then an approval request is created and assigned to approver 'A123' within 2 seconds And the routing decision (ruleId='R1', version='1.0', matchedConditions) is logged in the audit trail - Given the routing ruleset has no direct match for the submission When the requester submits the justification Then the system resolves the approver via org hierarchy to the requester's manager And the fallback resolution path is recorded in the audit trail
One-Click Approval/Deny Across Channels
- Given an approver receives a notification via email with one-click Approve and Deny links containing a secure, single-use token that expires in 24 hours When the approver clicks Approve Then the request status changes to Approved immediately and the action is idempotent on repeated clicks And the action source 'email' and approver context (IP, device) are captured in the audit log - Given an approver receives a mobile push notification with one-tap Approve/Deny When the approver taps Deny and provides a required reason Then the request status changes to Denied and the requester is notified within 60 seconds - Given an approver opens the web in-app notification center When they approve/deny from the web Then the outcome matches email/mobile behavior and tokens cannot be replayed
SLA Reminders and Escalation to Delegate
- Given SLA for step 1 is configured to 8 business hours in the approver’s local timezone with reminders at 4 hours and 15 minutes before breach When no action is taken by the SLA breach time Then a reminder is sent at 4 hours and 15 minutes before, and on breach the request escalates to the configured fallback delegate And all reminders and escalations are logged with timestamps - Given the fallback delegate does not act within 4 business hours after escalation When the secondary SLA is breached Then the request escalates to the next escalation target per ruleset and the requester is notified of the escalation
Multi-Step Approval Sequence
- Given ruleset R2 requires a two-step approval (Step 1: Product Lead; Step 2: Cost Center Owner) When Step 1 approver approves within SLA Then the system immediately routes the request to Step 2 and starts the Step 2 SLA timer And the request remains Pending Overall until Step 2 is approved - Given any step is Denied When the denial is recorded Then the overall request status becomes Denied and remaining steps are canceled
Conditional Auto-Approval for Predefined Scenarios
- Given an auto-approval rule R3 (v1.2) is configured for severity='Low' and estimated cost <= $100 and meeting duration <= 30 minutes When a matching justification is submitted Then the system automatically approves within 1 second without notifying a human approver And the auto-approval ruleId, version, and matched fields are recorded in the audit trail and shared with the requester
Auto-Propose Compliant Alternatives on Denial
- Given compliant-alternative suggestions are enabled with policy P1 requiring budget adherence and participants’ work windows When an approver denies a request and provides a reason Then the system generates at least 2 compliant alternative proposals within 30 seconds that meet policy P1 And the requester receives a notification with one-click options to accept an alternative or cancel - Given no compliant alternatives exist When the denial is recorded Then the system informs the requester within 30 seconds and provides the top 3 nearest-compliant suggestions with the violating constraints highlighted
Block Dispatch and Record Full Provenance
- Given a justification is Pending Approval When a meeting invite dispatch is attempted Then no calendar invitations are sent; the dispatch is queued with status 'Awaiting Approval'; and the UI shows 'Pending Approval' - Given final approval is recorded (including auto-approval) When dispatch resumes Then invites are sent within 30 seconds and the dispatch result is linked to the approval record - Given any approval/denial action occurs When the event is logged Then the audit record includes: actorId, role, action (approve/deny/auto), step number, timestamp (ISO 8601 with timezone), source channel (web/email/mobile), IP address, device/user agent, ruleId/version (if applicable), and geolocation (if available); records are immutable and exportable via audit API
Auto-Expiring Exception Windows
"As an organizer, I want approved exceptions to expire automatically so that flexibility doesn’t erode long-term fairness and policy compliance."
Description

Creates scoped, time-bound exceptions upon approval that automatically expire without manual intervention. Exceptions can target a single meeting, a series occurrence, or a defined window (e.g., next 48 hours), and can be limited to specific attendees or teams. Upon expiry, normal budgets and fairness rules re-apply. The system proactively reminds organizers before expiry and rescinds unused exceptions. All exceptions are visible in scheduling previews and reporting to avoid repeated overuse.

Acceptance Criteria
Approve Single-Meeting Exception with Auto-Expiry
Given a meeting request is blocked due to exhausted budget And the organizer submits a justification and selects scope "Single Meeting" When an approver approves the exception with an expiry timestamp T Then the system records exception {scope: single-meeting, meetingId, approverId, justification, createdAt, expiresAt (UTC), targets (optional)} And scheduling for that specific meeting bypasses budget and fairness checks until T or until the meeting is scheduled, whichever occurs first And the exception is visible in the meeting's scheduling preview with an expiry countdown And after T, the exception no longer applies
Approve Series-Occurrence Exception
Given a recurring meeting series with occurrence N blocked by budget or fairness rules And the organizer requests an exception scoped to "Series Occurrence N" When an approver approves the exception with an expiry timestamp T Then only occurrence N can bypass budget and fairness until T or until it is scheduled And all other occurrences in the series remain governed by normal rules And upon scheduling occurrence N, the exception status becomes consumed and cannot be reused
Time-Window Exception Scoped to Specific Team
Given team Alpha is specified as the target and budgets are exhausted When an approver grants an exception window starting now and ending at now+48h Then meetings whose attendees are all within team Alpha may bypass budget and fairness during the window And meetings including any attendee outside team Alpha are not affected by the exception And the exception's expiresAt is stored in UTC and displayed in the organizer's local timezone with a countdown And at exactly expiresAt the window becomes inactive without manual action
Pre-Expiry Reminder to Organizer
Given an active exception with expiresAt T and a default reminder lead time of 2 hours When the current time reaches T minus 2 hours and the exception is active and not yet consumed Then the system sends a reminder to the organizer via configured channels (in-app and email) including scope, targets, and T And if T occurs within 2 hours of creation, send the reminder immediately upon creation And the reminder delivery is logged with timestamp and delivery status
Automatic Expiry and Reversion to Normal Rules
Given an active exception reaches its expiresAt timestamp T When T is reached Then the exception status changes to expired And any new or pending scheduling attempts must pass budgets and fairness without the exception And any temporary holds or soft locks created under the exception are released And audit logs capture the expiry event with reason "auto-expired"
Rescind Unused Exceptions on Expiry
Given an exception window expires without any meeting scheduled under its scope When expiry occurs Then the exception is marked expired-unused And the organizer is notified that the exception was rescinded due to non-use And the exception no longer appears as selectable in scheduling flows And reporting reflects the exception as unused
Visibility in Scheduling Preview and Reporting
Given a user views the scheduling preview for a meeting covered by an active exception When the preview loads Then it displays an exception banner including scope, targets, approver, justification, and time to expiry And expired exceptions are not shown in the preview And Given an admin runs the Exceptions report for a date range When filtering by organizer or team Then the report shows counts of exceptions by status (active, consumed, expired-unused), average duration, and per-organizer/team usage in the period And exceptions used more than 3 times by the same organizer in 30 days are flagged as potential overuse
Immutable Audit Trail and Reporting
"As a compliance officer, I want a complete audit trail and reports of all overrides so that I can demonstrate accountability and tune policies based on real usage."
Description

Maintains an append-only audit log for all override events, including justification content, approvers, timestamps, policy versions, and resulting scheduling actions. Provides filters and export to CSV/JSON, and webhook streaming to SIEM/compliance tools. Dashboards summarize override frequency, after-hours impact avoided/incurred, approver response times, and top categories, enabling governance reviews and policy tuning. Ensures data retention and access controls aligned with org compliance requirements.

Acceptance Criteria
Append-Only Log Entry Creation
Given a budget-exhausted override is submitted with justification and approver decision captured, When the system records the override, Then it appends a new audit entry containing: tenant_id, override_id, correlation_id, actor_id, actor_role, team_id, meeting_id, justification_text, justification_category, approver_ids, approver_decisions, submitted_at, decided_at, policy_version_id, resulting_action, after_hours_impact_minutes, request_timezone, created_at, created_by_service. Given the audit entry is persisted, When clients attempt to update or delete it via any API or UI, Then the operation is rejected and no mutation occurs. Given an admin performs a correction, When a correction is needed, Then a new audit entry with type="correction" is appended referencing the original override_id and prior entry sequence_number. Given any timestamp fields are stored, When retrieved, Then they are in ISO 8601 UTC with millisecond precision. Given sequence numbering per-tenant, When a new entry is appended, Then it is assigned a monotonically increasing sequence_number unique within tenant.
Tamper-Evident Integrity Chain
Given an audit entry is appended, When hashes are computed, Then current_hash is calculated over the canonical serialized payload and previous_hash equals the current_hash of the prior entry in the same tenant chain. Given the integrity verification job runs, When validating the chain for a tenant and time range, Then it returns status="pass" if every previous_hash/current_hash pair matches and sequence is contiguous with no gaps. Given any stored entry is altered outside the append process, When verification runs, Then it returns status="fail" with the first broken sequence_number and emits an audit_integrity_failed event. Given log segmentation or archival occurs, When verifying across segment boundaries, Then the chain integrity is preserved using segment_root_hash and anchor_hash linking adjacent segments.
Audit Log Filtering and Pagination
Given audit data exists, When filtering by any combination of tenant_id, date range (submitted_at), actor_id, approver_id, decision outcome, justification_category, policy_version_id, meeting_id, and after_hours_impact_minutes (> 0, = 0), Then only matching entries are returned. Given a sort parameter, When sort=created_at:desc or created_at:asc or sort=after_hours_impact_minutes:desc, Then results are ordered accordingly. Given pagination parameters, When page_size is omitted, Then default page_size=100; When page_size is provided, Then values > 0 and <= 1000 are accepted. Given cursor-based pagination, When the next_cursor from a response is supplied, Then the subsequent page continues without duplicates or gaps. Given datasets up to 500,000 entries for a tenant, When applying a supported filter, Then the API responds within 2 seconds p95.
Export to CSV and JSON
Given a filter is applied, When exporting to CSV, Then the generated file contains a header row and one row per matched entry with values properly escaped, timestamps in UTC ISO 8601, and field order documented: sequence_number, tenant_id, override_id, correlation_id, actor_id, actor_role, team_id, meeting_id, justification_text, justification_category, approver_ids, approver_decisions, submitted_at, decided_at, policy_version_id, resulting_action, after_hours_impact_minutes, created_at, current_hash, previous_hash. Given a large export (>100,000 rows), When requested, Then the system provides a streaming download or pre-signed URL and completes generation without timeouts. Given JSON export is selected, When requested, Then the response is newline-delimited JSON (NDJSON) with one canonical entry per line including hash fields. Given role-based access, When a user without export permission attempts export, Then the request is denied with 403 and no file is generated. Given an export job completes, When the client downloads the file, Then a checksum (SHA-256) is provided to verify file integrity.
Webhook Streaming to SIEM
Given a webhook destination with shared secret is configured and enabled, When a new audit entry is appended, Then the system POSTs the event within 5 seconds including headers X-TimeTether-Signature (HMAC-SHA256), X-TimeTether-Timestamp, and an idempotency key event_id. Given the receiver returns a 2xx, When the event is acknowledged, Then the event is marked delivered and not retried. Given the receiver returns 5xx or times out, When delivery fails, Then retries occur with exponential backoff for up to 24 hours and per-tenant ordering is preserved. Given the receiver returns 4xx (except 429), When delivery fails, Then the event is dropped and marked failed with reason; for 429, retry with backoff and respect Retry-After. Given the webhook is re-enabled after downtime, When backlog exists, Then events are delivered in original sequence_number order and no duplicates are delivered to an idempotent receiver.
Governance Dashboards Metrics Accuracy
Given a selected time range and filters, When viewing the dashboard, Then override count equals the number of matching audit entries and top categories reflect the highest frequency justification_category values. Given after-hours impact metrics, When computed, Then avoided_minutes and incurred_minutes equal the sums of after_hours_impact_minutes partitioned by sign across matching entries. Given approver response time metrics, When computed, Then median and p95 values equal those calculated from decided_at - submitted_at across matching entries. Given filters are changed, When applied, Then all charts and KPIs update within 2 seconds and reflect the filter state. Given new audit entries are appended, When within the dashboard auto-refresh interval (<=5 minutes), Then metrics include the new data without manual refresh.
Access Control and Data Retention
Given organization RBAC, When a user with role in [Admin, Compliance] accesses the audit log, Then full entry content including justification_text is visible; When a user with other roles accesses, Then justification_text is masked and export endpoints are inaccessible. Given API access tokens include scopes, When a request lacks the required scope (audit:read or audit:export), Then it is rejected with 403 and an audit_access_denied entry is appended. Given data retention is configured (e.g., 7 years) and no legal hold is active, When entries exceed retention, Then a purge job permanently deletes them, appends an audit_purged summary entry, and ensures related backups are aged out per policy. Given a legal hold is applied to a tenant or case, When retention would otherwise purge entries, Then those entries are retained until the hold is released and the hold action is logged. Given data residency is set to a region, When storing and exporting audit data, Then data remains in the selected region’s storage and egress respects residency controls.
Policy and Budget Configuration Console
"As an administrator, I want to configure budgets and override policies centrally so that the system enforces fairness while allowing controlled flexibility for critical events."
Description

Offers an admin UI and APIs to configure budgets, override rules, justification fields, approval routing, exception durations, and notification SLAs. Supports templates by team/role, inheritance with overrides, and versioning with effective dates. Validates configurations, simulates policy impacts on historical data, and provides safe-rollout mechanisms (dry-run, partial cohorts). Changes propagate in near real time to the scheduling engine and are fully auditable.

Acceptance Criteria
Budget Policy Versioning with Effective Dates
Given I am an admin with access to the Policy and Budget Configuration Console When I create a budget policy with name, scope (org/team/role), limits, and set effective_start and optional effective_end, and save as a new version Then the console validates that effective windows for the same scope do not overlap and that currency and time units are consistent And the policy is saved with an incremented version number and timestamps And querying the policy via UI and API with a reference datetime returns the correct active version And a preview shows upcoming active policies for the next 90 days
Template Inheritance and Overrides by Team/Role
Given a base policy template exists When I create a team template that inherits the base and override specific fields (e.g., budget_limit, approval_route) Then the resolved policy displays inherited and overridden fields, with overrides taking precedence And precedence order is user > role > team > org default, with deterministic resolution And conflicts or cyclic inheritance are blocked with explicit error messages before save And the API returns the resolved policy for a given user within 500 ms
Justified Override Workflow Configuration
Given budgets may be exhausted When I configure override rules to require justification with required fields (reason, impact, duration) and minimum length of 20 characters, and define approver routing by role with fallbacks Then the console enforces field presence and length at runtime and stores the justification schema version And an override request triggered by the scheduling engine includes the configured fields and routes to the correct approvers And notification SLAs (channels, retries, escalation timeouts) are configurable and applied to approval requests And audit entries record requester, justification snapshot, approver decisions, and timestamps
Exception Duration and Auto-Expiry of Overrides
Given an override is approved with a configured exception duration When the exception reaches its expiry time Then the exception automatically deactivates and future requests require new approval And pre-expiry notifications are sent according to the configured number of minutes before expiry And the scheduling engine no longer applies the exception after expiry with a propagation delay of no more than 30 seconds And audit logs capture activation and expiry events with the associated policy version
Configuration Validation and Safe Save
Given I attempt to save a policy configuration When required fields are missing, numeric ranges are invalid, or approval routes are cyclic or unreachable Then the console blocks save and presents field-level errors with codes and remediation hints And a Validate action runs all checks without saving and returns Pass/Fail with a checklist of validations And only valid configurations can be versioned and promoted to rollout stages
Dry-Run Simulation and Partial Cohort Rollout
Given a draft policy version exists When I run a dry-run simulation against the last 90 days of historical scheduling data for selected teams Then the console shows impact metrics (e.g., overrides triggered, approvals required, after-hours meetings impacted) and no production changes occur And I can enable rollout to a partial cohort defined by percentage or explicit teams/users, with a unique experiment key And I can rollback to the previous version with one action, restoring prior behavior within 60 seconds And cohort assignment and rollout/rollback events are recorded in the audit log
Near Real-Time Propagation and Full Auditability
Given a policy change is saved and activated When the scheduling engine requests the current policy Then the change is visible within 30 seconds in both UI and API responses And a propagation status indicator shows Pending, In-Progress, or Applied with timestamps And 100% of changes are written to an immutable audit log including before/after diffs, actor, source IP, and correlation ID And audit entries are queryable and exportable via CSV and API with filters for date range, actor, team, and policy version

Series Caps

Set per-series credit limits so a single ceremony can’t drain the team’s entire budget. Ensures equitable distribution across rituals and nudges right-sizing of frequency and duration.

Requirements

Cap Configuration & Hierarchies
"As a team lead, I want to set and manage per-series credit caps with sensible defaults so that no single ceremony can drain our meeting budget."
Description

Provide per-series credit cap configuration with hierarchical defaults at workspace, team, and individual series levels. During series creation/edit, owners can enable a cap, set the limit, choose cap behavior (soft warn vs hard block), and select the budget source. Caps inherit from workspace defaults with the ability to override at team or series level, including templates for common ceremonies (standup, planning, retro). Enforce role-based permissions (admins define defaults; series owners adjust within allowed ranges). Persist settings in TimeTether so they are referenced by scheduling, fairness rotation, and invite services. Display cap metadata in the series summary and scheduler UI so organizers see constraints before proposing changes.

Acceptance Criteria
Workspace Defaults and Ceremony Templates Configuration
Given I am a workspace admin on the Caps settings page When I enable the default series cap, set a numeric limit, choose cap behavior (Soft Warn or Hard Block), define allowed override range (min/max), select a default budget source, and click Save Then the default cap configuration is persisted at the workspace level and a success confirmation is shown And an audit event is recorded with actor, changes, timestamp, and scope And validation prevents saving if required fields are missing or values are outside the allowed range with inline error messages And ceremony templates (Standup, Planning, Retro) are available with predefined cap values and behavior And selecting a template auto-populates fields which can be saved and applied as workspace defaults for that template
Team-Level Cap Override Within Allowed Range
Given workspace defaults exist and I am a team admin When I open my team’s Cap settings, enable an override, and set a limit and behavior within the workspace-defined allowed override range, then click Save Then the team override is persisted and becomes the effective cap for all series in the team that have no series-level override And attempting to set a value outside the allowed range is blocked with an inline error and the Save action is disabled And existing series without a series-level override reflect the new effective cap on next settings load and next scheduling evaluation And an audit event is recorded with team scope
Series-Level Cap with Soft Warn
Given I am a series owner creating or editing a series that inherits a cap When I enable a series-level cap override, select Soft Warn behavior, choose a budget source, set a limit within allowed ranges, and save Then the series-level cap is persisted and becomes the effective cap for the series When I propose scheduling changes (e.g., frequency/duration) or add occurrences that would exceed the cap based on the selected budget source Then the scheduler shows a warning modal with remaining credits, projected overage, and options to Proceed or Cancel And choosing Proceed records consent in the audit log, allows the change, and sends invites And choosing Cancel aborts the change and no invites are sent
Series-Level Cap with Hard Block
Given I am a series owner with a series-level cap set to Hard Block When I attempt to save scheduling changes or add occurrences that would exceed the cap based on the selected budget source Then the operation is blocked, a clear error banner states the cap limit and required reduction, and the Save/Send Invites actions remain disabled And no invites or updates are sent to participants And the scheduler provides a link or controls to adjust frequency/duration to fit within the cap And an audit event records the blocked action with overage details
Role-Based Permissions Enforcement
Given role assignments exist (workspace admin, team admin, series owner, viewer) When a user without the required role attempts to modify workspace defaults via UI or API Then the UI controls are disabled or hidden and API requests return HTTP 403 with error code RBAC_CAPS_FORBIDDEN, and an audit event is recorded When a team admin modifies only their team override within allowed ranges Then the change succeeds; attempts to edit workspace defaults are blocked with 403 and UI messaging When a series owner edits only their series-level cap within allowed ranges Then the change succeeds; attempts by non-owners on the series are blocked with 403 and UI messaging
Cap Metadata Display in Series Summary and Scheduler
Given a series has an effective cap (inherited or overridden) When I view the series summary page Then I see a Cap card showing limit (credits), behavior (Soft Warn/Hard Block), budget source, and effective level (Workspace/Team/Series/Template) And remaining credits relative to the cap are displayed and updated after scheduling changes When I open the scheduler for that series before proposing changes Then the same cap metadata is visible and accessible via tooltip/help, ensuring organizers see constraints prior to submission
Cross-Service Persistence and Enforcement
Given a series cap is configured and saved When the scheduling service evaluates a proposed change Then it queries the cap settings and enforces Soft Warn or Hard Block behavior accordingly When the fairness rotation service generates rotation proposals Then it reads the cap settings and does not produce proposals that would exceed Hard Block caps, and flags Soft Warn scenarios for organizer review When the invite service processes a change Then it sends invites only if the action is allowed under the effective cap; blocked actions result in no outbound invites and an error returned to the caller And updates to cap settings are reflected across services on the next request without stale reads
Real-time Usage Calculation & Forecasting
"As a scheduler, I want to see remaining credits and projected usage for a series in real time so that I can right-size frequency and duration before we overspend."
Description

Implement a usage accounting engine that calculates consumed and projected credits per series in real time. Consumption derives from scheduled/held instances (duration × participants × credit rate) and accounts for cancellations, reschedules, skipped holidays, and timezone-aligned work windows. Forecasting projects remaining spend across the series cadence for the current budget cycle, updating instantly when organizers change frequency, duration, or attendee lists. Expose remaining credits, burn rate, and time-to-cap in the scheduler, series page, and planning views. Support what-if simulations to compare alternate cadences and durations before saving changes.

Acceptance Criteria
Scheduler edits trigger real-time forecast update
Given a series with a defined cadence, duration, attendee list, credit rate, and series cap When the organizer modifies frequency, duration, or attendees in the Scheduler editor without saving the page Then remaining credits, burn rate, and time-to-cap recalculate and visually update within 1 second And the values equal those returned by the usage API for the same timestamp And no full page reload occurs
Consumption and projection reflect held, canceled, rescheduled, and skipped instances
Given a series with instances in statuses: held (completed), scheduled (future), canceled, and rescheduled Then consumed_credits equals sum over held instances of (duration × participant_count × credit_rate) And projected_credits equals sum over scheduled future instances within the current budget cycle of (duration × participant_count × credit_rate) And canceled instances contribute 0 to both consumed and projected And rescheduled instances contribute only once at the new date/time And instances auto-skipped for configured holidays contribute 0 When any instance status changes (held, canceled, rescheduled) or a holiday rule applies Then the totals update within 1 second
Forecast constrained to current budget cycle with time-to-cap
Given a current budget cycle with start_date and end_date and a series cap Then projections include only occurrences between now and end_date And time-to-cap equals the earliest projected occurrence when (consumed + cumulative_projected) meets or exceeds the cap within the cycle And remaining_credits = max(0, cap - (consumed + projected_within_cycle)) When the cycle rolls over to a new period Then all forecast metrics recompute for the new cycle within 1 second
Metrics consistent across Scheduler, Series, and Planning views
Given the same series is open in Scheduler, Series page, and Planning views When any change affecting usage is saved Then remaining credits, burn rate, and time-to-cap display identical values across all three views And each view refreshes the metrics within 2 seconds of the saved change without requiring a manual reload
What-if simulation compares alternate cadences/durations without persisting
Given the organizer opens What-if mode for a series When the user adjusts cadence, duration, or attendee list Then simulated remaining credits, burn rate, and time-to-cap are shown within 1 second, alongside the current baseline And no persistent series data changes are committed until Save is clicked And on Cancel/Exit, all simulated values are discarded and the baseline remains unchanged And on Save, simulated values become the new baseline and match the persisted usage metrics
Timezone and holiday rules applied to cadence generation and projection
Given attendees across multiple timezones with configured work windows and holiday calendars When projecting future instances for the current cycle Then each instance's start/end is computed in local time respecting DST transitions And instances falling on configured holidays for the series are skipped And projected credits exclude skipped instances and use the actual duration after timezone conversions And recalculation completes within 1 second after any work window or holiday configuration change
Attendee list changes recalculate projected spend
Given a series with an attendee list and scheduled future instances in the current cycle When attendees are added to or removed from the series Then projected credits for all future instances in the current cycle recompute using the updated participant_count And consumed credits for already held instances remain unchanged And updates appear within 1 second in Scheduler, Series page, and Planning views
Enforcement with Smart Suggestions
"As a meeting organizer, I want the system to block or suggest adjustments when a series would exceed its cap so that we stay within budget without sacrificing essential collaboration."
Description

Apply cap-aware scheduling guardrails across creation and edits. On attempts that would exceed the cap, enforce configured behavior: block (hard cap) or warn with clear impact (soft cap). Provide one-click, data-backed alternatives such as shortening duration, reducing frequency, rotating attendees, or skipping weeks with holidays to remain within the cap. Adjust suggestions to respect team timezones, work windows, and fairness rotation policies. Indicate the credit effect of each suggestion and allow immediate apply-and-update of the series and invites.

Acceptance Criteria
Hard Cap Block on New Series
Given a per-series hard cap is configured and the proposed schedule’s projected credits exceed the remaining cap When the user attempts to save the new series Then the system blocks creation, displays an overage message with exact credits over cap, disables the save action, and sends no invites And then the system presents at least three cap-compliant suggestions (e.g., shorten duration, reduce frequency, skip holiday weeks, rotate attendees), each with labeled credit delta and final total And then all displayed suggestions meet the cap and adhere to team timezones, work windows, and fairness rotation policies And then an audit event "cap_blocked" is recorded with seriesId, proposedCredits, capRemaining, and userId
Soft Cap Warning on Edit
Given a per-series soft cap is configured and an edit would exceed the remaining cap When the user attempts to save the edit Then a modal warns with exact overage (credits and % of cap) and lists cap-compliant suggestions And then Proceed is enabled only after explicit confirmation (checkbox + Confirm) And then choosing Proceed applies the edit and logs "cap_warning_proceed"; choosing a suggestion applies it and logs "suggestion_applied"; cancelling leaves the series unchanged and logs "cap_warning_cancel"
One-Click Apply Suggestions and Update Invites
Given cap-compliant suggestions are displayed When the user clicks Apply on a suggestion Then the series parameters update without additional form edits, projected credits recalc to at-or-below cap, and updated calendar invites are sent to affected attendees And then a success confirmation appears within 5 seconds or an error appears with no partial changes if sending fails And then change history records suggestion type, before/after credits, and actor
Suggestions Respect Timezones, Work Windows, and Fairness
Given participants have configured timezones, work windows, and fairness rotation rules When scheduling suggestions are generated Then each displayed suggestion schedules all occurrences within every included participant’s work window and complies with active fairness rotation constraints And then any suggestion that cannot satisfy these constraints is not displayed
Credit Impact Display and Real-Time Cap Calculation
Given the series editor is open When the user changes duration, frequency, attendees, or excludes occurrences Then projected credits and remaining cap update within 300ms and match backend calculation within 1 credit And then each suggestion displays both delta credits (+/−) and final projected credits vs cap (e.g., "−6 → 18/20") And then over-cap states are visually flagged and in-cap states are clearly indicated
Holiday-Aware Skip Weeks Suggestion
Given team holiday calendars are enabled When an over-cap schedule is detected Then suggestions include skipping weeks where any included participant has a public holiday And then selected skip suggestions reduce projected credits by exactly skipped-occurrence count × per-occurrence credit and avoid scheduling on holiday dates And then applying the suggestion updates the series and sends updated invites only for affected instances
Proactive Notifications & Nudges
"As a series owner, I want timely alerts as we approach the cap along with actionable recommendations so that I can adjust before we overspend."
Description

Deliver proactive, actionable alerts to series owners and relevant admins when usage approaches or exceeds thresholds (e.g., 50%, 75%, 90%, 100%). Support channels including in-product banners, email, and Slack with deep links back to the series. Include contextual recommendations (reduce duration, adjust cadence, rotate attendees) with estimated savings. Provide weekly digests summarizing series at risk and upcoming instances likely to breach the cap. Allow users to configure thresholds and channels per series or inherit workspace defaults.

Acceptance Criteria
Threshold Crossing Notifications at 50/75/90/100
Given a series with an active Series Cap and configured thresholds (e.g., 50%, 75%, 90%, 100%) When cumulative usage for the current cap period crosses upward to meet or exceed a configured threshold for the first time in that period Then notifications are sent within 5 minutes to the series owner(s) and relevant admins via each enabled channel (in-product banner, email, Slack) And the notification includes the series name, current usage percentage, remaining credits/time until cap, cap reset date, and a deep link to the series details/cap settings And the 100% threshold notification explicitly labels the status as “Cap Breached” and indicates any immediate impact (e.g., next instance will exceed cap) And the system sends at most one notification per threshold per series per cap period (no duplicates for fluctuations above the same threshold) And if the cap period resets, the threshold notification counters reset for the new period
Per-Series Threshold & Channel Configuration with Workspace Defaults
Given workspace-level default thresholds and channel preferences are defined When a series owner opens the series’ Notifications & Nudges settings Then the UI displays the inherited thresholds and channels from the workspace by default And the owner can override thresholds (add/remove/change values in 1–100%) and select/deselect channels (banner, email, Slack) for the series And saving persists overrides for that series and they take effect within 5 minutes And if Slack is not connected for the workspace, the Slack option is disabled with explanatory messaging And if a series has no per-series overrides, it continues to inherit future changes to workspace defaults automatically And validation prevents saving if no channel is selected or if thresholds include duplicates or out-of-range values
In-Product Banners with Deep Links and Dismiss Behavior
Given a user is a series owner or relevant admin and is logged into TimeTether And a series they manage has crossed a configured threshold in the current period When the user views any page within the product Then a banner appears within the session, indicating the threshold crossed, series name, current usage, and a CTA button linking directly to the series details/cap settings And dismissing the banner hides it for that user for that series for 7 days or until a higher threshold is crossed (whichever comes first) And the banner is keyboard navigable, screen-reader accessible (role=alert), and meets WCAG AA contrast And the deep link opens the correct series with the caps section in focus within 2 seconds in 95% of attempts
Weekly Digest Summarizing At-Risk and Predicted Breaches
Given the workspace has at least one series with usage >= a configured warning threshold or with forecasted breach in the next 7 days When the weekly digest job runs on the configured schedule Then each series owner and relevant admin receives a single digest message via their selected channel(s) summarizing: - Series currently at or above thresholds reached in the past week - Series forecasted to breach their caps within the next 7 days, with the predicted date - Key metrics: current usage %, remaining credits/time, next instance date, and deep links to each series And the digest excludes series the recipient does not own/admin And the digest is delivered within the scheduled window with a 99% success rate and no duplicate sends per recipient
Contextual Recommendations with Estimated Savings
Given a threshold notification or digest is generated for a series When the message is composed Then it includes at least one actionable recommendation relevant to reducing usage (e.g., reduce duration by X minutes, adjust cadence from weekly to biweekly, rotate attendees) And each recommendation includes an estimated savings value (credits/time) for the current cap period based on the series’ current attendee count, cadence, and duration And if the recommended change is applied, the realized savings calculated by the system is within ±10% of the estimate under unchanged attendance patterns And the CTA/deep link brings the user to the series edit view with the relevant setting highlighted
Recipient Targeting, Rate Limiting, and Audit Logging
Given a threshold crossing event occurs for a series When determining recipients Then notifications are sent to all series owners and workspace admins with active accounts and valid contact methods, and not sent to deactivated users And the system rate-limits to one notification per threshold per series per recipient within a 24-hour window, while still sending new notifications for higher thresholds crossed And all sends (channel, recipient, timestamp, series, threshold) are recorded in an audit log And if delivery fails for a channel, the failure is logged with reason and retried up to 3 times over 15 minutes
Admin Override & Audit Trail
"As an admin, I want the ability to temporarily override a series cap with a recorded reason so that critical meetings can proceed while maintaining accountability."
Description

Enable authorized admins to override caps for specific series or time-bound windows with a required reason, optional expiry, and cap delta. All overrides are logged with actor, timestamp, previous/new values, and justification. Display override status on the series page and include it in reports and exports. Provide an approval workflow (optional) and notifications to series owners when overrides are applied or expire. Ensure audit data is immutable and queryable for compliance.

Acceptance Criteria
Single-Series Override with Reason and Expiry
Given an authorized admin with override permissions And a series with an existing cap value When the admin submits an override with a non-empty reason, a numeric cap delta (+/-), and an optional future expiry timestamp (UTC) Then the system applies the new effective cap immediately as previous_cap + delta And the series page displays an "Override Active" indicator with the reason and the localized expiry timestamp And the scheduling engine respects the effective cap for all subsequent calculations within 60 seconds And an audit entry is appended capturing actor, action=create_override, timestamp (UTC), series_id, previous_cap, new_cap, delta, justification, and expiry (nullable) And series owners receive an in-app notification and an email containing the override details within 60 seconds
Authorization and Field Validation for Overrides
Given a user without override permission When the user attempts to create, modify, or revoke an override Then the request is rejected with 403 Forbidden and no cap changes occur Given an authorized admin When the admin submits an override without a reason Then the request is rejected with 422 Unprocessable Entity indicating reason is required Given an authorized admin When the admin submits an override with an expiry in the past Then the request is rejected with 422 Unprocessable Entity indicating expiry must be in the future Given an authorized admin When the admin submits an override with a delta of 0 or that would produce a negative resulting cap Then the request is rejected with 422 Unprocessable Entity indicating invalid delta or resulting cap Given an authorized admin When the admin submits fields with incorrect types (e.g., non-numeric delta, malformed timestamp) Then the request is rejected with 400/422 and field-level error messages are returned
Auto-Expiry Reverts Cap and Notifies Stakeholders
Given an active override with a stored previous cap value and a future expiry timestamp When the system time reaches the expiry timestamp Then the system reverts the series cap to the stored previous cap within 60 seconds And the "Override Active" indicator is removed from the series page And an audit entry is appended with actor=system, action=expire_override, timestamp (UTC), series_id, previous_cap (effective before revert), new_cap (reverted), justification (auto-expiry), and expiry And series owners receive an in-app notification and an email indicating the override has expired within 60 seconds And the operation is idempotent so repeated expiry processing does not create duplicate audit entries or multiple notifications
Optional Approval Workflow for Overrides
Given the organization setting "Overrides Require Approval" is enabled And an authorized admin submits an override request with reason, delta, and optional expiry When the request is submitted Then the request is saved in Pending state and does not change the effective cap And designated approvers receive an in-app notification and email with approve/deny actions When a designated approver approves the request Then the override is applied immediately, all notifications are sent to series owners, and an audit entry is appended with action=approve_override including approver and timestamps When a designated approver denies the request Then no cap change occurs, the requester is notified of the denial, and an audit entry is appended with action=deny_override including approver and timestamps Given the organization setting is disabled When an authorized admin submits an override Then the override applies immediately without an approval step and is logged accordingly
Reporting and Exports Include Override Metadata
Given one or more series have active or historical overrides When a user generates the Series Report (UI) or CSV export for a selected date range Then each series row includes override_active (boolean), override_actor, override_reason, override_applied_at (UTC), override_previous_cap, override_new_cap, override_delta, override_expiry (nullable), and approval_status (if applicable) And filters allow "Has Active Override" and "Overrides Within Date Range" And the REST API for reports returns the same fields in JSON for parity with the CSV And totals and summaries reflect the effective cap values for the selected period
Immutable, Queryable Audit Trail for Overrides
Given any override lifecycle event (create, approve, deny, expire, revoke) When the event occurs Then an audit entry is appended with immutable fields: id, series_id, actor_id (or system), action, timestamp (UTC), previous_cap, new_cap, delta (nullable), justification, expiry (nullable), and correlation_id (nullable) And the audit store is append-only: attempts to update or delete entries via UI or API return 405/409 and are logged as security events without modifying stored entries And the audit log is queryable by series_id, actor_id, action type, and time range, returning up to 10,000 records within 2 seconds for indexed queries And an audit export endpoint provides a verifiable checksum for the result; a verification endpoint confirms checksum validity to demonstrate no tampering
Manual Revocation of Active Override
Given an active override exists for a series When an authorized admin selects Revoke Override, provides a non-empty reason, and confirms Then the system immediately reverts the cap to the stored previous cap And the series page removes the "Override Active" indicator And series owners receive an in-app notification and an email indicating the override was revoked including the reason within 60 seconds And an audit entry is appended with actor, action=revoke_override, timestamp (UTC), series_id, previous_cap (effective before revoke), new_cap (reverted), and justification (revoke reason)
Series Cap Insights & Reporting
"As an ops analyst, I want reports on cap usage and trends by series so that I can optimize our ritual mix and budget allocation."
Description

Offer a dashboard with per-series and aggregate views of cap settings, usage, remaining credits, breach events, and trendlines over time. Include filters by workspace, team, owner, ceremony type, and date range. Surface fairness indicators (e.g., distribution of credits across rituals) and ROI hints (e.g., credits vs. attendance/decisions). Provide CSV export and scheduled reports to stakeholders. Forecast future cap risk based on cadence and historical burn to inform planning cycles.

Acceptance Criteria
Dashboard Aggregate and Per-Series Views Render
Given seeded data across multiple workspaces and series When a user with access opens the Series Cap Insights dashboard with the default Last 90 Days date range Then the dashboard renders sections: Aggregate Summary, Per-Series Table, Trendlines, and Breach Event Log And Aggregate Summary displays totals: total_credits_allocated, credits_burned, credits_remaining, breach_events_count, unique_series_count, with values matching backend aggregates within 0.1% And the Per-Series Table shows columns: series_id, series_name, owner, team, ceremony_type, cap_limit, credits_burned, credits_remaining, breaches, last_activity_at; each column is sortable ascending/descending And Trendlines are bucketed weekly; number of points equals number of whole weeks in the selected range; tooltips show bucket start date and values And Breach Event Log count equals Aggregate Summary breach_events_count for the same filters And p95 API latency for insights data is ≤ 2.5s for datasets up to 10k events in range; above-the-fold content renders within 3.0s p95
Multi-Dimensional Filtering and URL State
Given data exists across multiple workspaces, teams, owners, ceremony types, and dates When the user applies filters workspace=ACME, team=Platform, owner=alice, ceremony_type=Standup, date_range=custom (2025-01-01 to 2025-03-31) Then all dashboard sections recompute to reflect the intersection (AND) of filters and only matching rows/series are shown And the Aggregate Summary, Per-Series Table counts, Trendlines, and Breach Event Log align numerically under the active filters And the URL encodes the filter state; reloading or sharing the link restores the exact filter configuration And clearing filters resets to default Last 90 Days and All values And date presets (Last 7/30/90 days, This/Last quarter) and custom ranges are available and computed in the viewer’s timezone And pagination and sorting respect active filters And an informative empty state appears when no results match
Fairness Indicators Surface Distribution Across Rituals
Given multiple series with varying credit burn When the user opens the Fairness panel Then the system computes and displays: per-ritual credit share (%), Gini coefficient across series in the selected scope, and Top 10% vs Bottom 50% share ratio And an Equity Score (0–100) is shown with badges: Green ≥ 80, Amber 60–79, Red < 60 And series whose burn rate exceeds 150% of the median are flagged as outliers with an icon and tooltip And tooltips define each metric and its formula And fairness metrics are included per series in exports with fields: equity_score, gini, outlier_flag
ROI Hints Correlate Credits With Attendance and Decisions
Given a series has attendance and decision data available for the selected date range When the ROI panel is viewed Then the following metrics are displayed: credits_per_attendee, credits_per_decision, attendance_rate (%), decision_rate (%), computed from filtered data And a scatterplot of credits_burned vs average_attendance renders with a best-fit trendline and shows Pearson R with two-decimal precision And clicking a point highlights the corresponding series row in the table And if any input data is missing, the panel shows an “Insufficient data” message and hides the affected metric without throwing errors
CSV Export Reflects Current View and Filters
Given the user has applied any combination of filters and sorting When Export CSV is triggered Then the downloaded file contains only rows within the current filters and date range and respects current sorting And the file uses UTF-8 encoding, comma delimiter, header row present; timestamps are ISO 8601 in UTC; numeric fields are unformatted numbers And file name follows pattern: timetether_series_cap_insights_{workspace_or_all}_{YYYYMMDD}.csv And fairness and ROI columns are included when available for each series; missing values are blank And for exports > 100k rows, an asynchronous export is queued, a toast confirms, and a download link is emailed within 5 minutes And a 100-row random sample from the CSV matches API values exactly for those rows
Scheduled Reports to Stakeholders
Given the user configures a scheduled report with recipients, frequency, time, timezone, and saved filter set When the schedule is saved Then inputs are validated (emails format and domain policy, time, timezone, non-empty recipients) and duplicates are prevented And at the scheduled local time, recipients receive an email with a link restoring the saved filters and an attached CSV matching the saved view And delivery outcomes are logged; on failure, the system retries up to 3 times with exponential backoff and notifies the scheduler on final failure And recipients can unsubscribe via a link; unsubscribed addresses are excluded from future sends and logged And modifying or pausing a schedule updates subsequent deliveries accordingly
Forecast Future Cap Risk and Alerts
Given at least 12 weeks of historical weekly burn for a series When the Forecast panel is viewed Then the system projects remaining credits and burn for 30/60/90 days with 50% and 90% confidence intervals And risk levels are assigned: High if projected breach before period end with probability ≥ 70%, Medium for 40–69%, Low otherwise And the trendline includes an annotation for the projected breach date if within the next 90 days And an assumptions panel shows cadence used and last-12-week average burn; adjusting cadence for what-if updates the forecast within 1 second And model backtest on the last 8 weeks yields MAPE ≤ 20%; otherwise, display “Insufficient history for reliable forecast” and suppress risk level
Caps API & Webhooks
"As a platform integrator, I want APIs and webhooks for cap settings and events so that I can automate governance and keep external tools in sync."
Description

Expose REST endpoints to create, read, update, and delete series caps; retrieve usage and forecasts; and list threshold events. Publish webhooks for threshold crossings, enforcement blocks, and override changes to enable external automations (e.g., auto-create Jira tasks, notify finance). Secure with OAuth scopes and per-series permissions, apply rate limits, and ensure idempotency. Provide versioned schemas and examples so integrators can reliably sync cap policy with external systems.

Acceptance Criteria
Create Series Cap via REST with Idempotency
Given a valid OAuth token with scope caps.write and permission to series S When the client POSTs /v1/series/{S}/caps with JSON {limit, period, currency, starts_on} and header Idempotency-Key=K Then the server returns 201 Created with Location header, cap_id, X-Api-Version: v1, and a body matching schema timeTether.caps.v1.cap And repeating the same POST with the same Idempotency-Key K within 24h returns 200 OK with identical body and header Idempotency-Replayed: true And exactly one cap resource exists for series S with those attributes
Read/Update/Delete Series Cap with Permissions and ETags
Given a valid token with scope caps.read and permission to series S When GET /v1/series/{S}/caps/{cap_id} Then 200 OK is returned with ETag and a body matching schema timeTether.caps.v1.cap and header X-Api-Version: v1 And the same request with a token lacking scope or permission returns 403 Forbidden with error code insufficient_scope or forbidden_series When PATCH /v1/series/{S}/caps/{cap_id} with If-Match set to the prior ETag and JSON {limit} Then 200 OK is returned with updated values and a new ETag And using a stale ETag returns 412 Precondition Failed When DELETE /v1/series/{S}/caps/{cap_id} Then 204 No Content is returned and subsequent GET returns 404 Not Found
Retrieve Usage and Forecast for a Series Cap
Given a valid token with scope usage.read and an existing cap on series S When GET /v1/series/{S}/caps/{cap_id}/usage?from=2025-09-01T00:00:00Z&to=2025-09-30T23:59:59Z Then 200 OK is returned with body matching schema timeTether.caps.v1.usage containing fields current_total, projected_total, remaining, forecast_status And timestamps are normalized to the series timezone and inputs outside the cap period are rejected with 400 Bad Request And forecast_status equals within_cap when projected_total <= limit or breach_by when projected_total > limit with the difference reported And when include=breakdown is provided, a per-meeting breakdown array is returned
List Threshold Events with Filtering and Pagination
Given a valid token with scope events.read and permission to series S When GET /v1/cap-events?seriesId={S}&type=threshold_crossing,enforcement_block,override_change&since=2025-09-01T00:00:00Z&pageSize=50 Then 200 OK is returned with a body matching schema timeTether.caps.v1.event_list ordered by occurred_at ascending And only events belonging to series S are included; events from other series are excluded And response includes cursor-based pagination fields and header X-Next-Cursor when more results exist And invalid filter values (e.g., type=foo) return 400 Bad Request with error code invalid_filter
Webhook Delivery for Threshold Crossing: Signing, Retries, Versioning
Given a registered webhook endpoint E subscribed to threshold_crossing with shared secret H When any cap for series S crosses 80% of its limit Then a POST is sent to E within 60 seconds containing event_id, occurred_at, series_id, cap_id, percentage, and payload matching schema timeTether.webhooks.v1.threshold_crossing And headers include X-TT-Event-Type: threshold_crossing, X-TT-Schema-Version: v1, and X-TT-Signature with HMAC-SHA256 over the body using H And if E responds non-2xx, retries occur with exponential backoff for up to 10 attempts; a later 2xx stops further retries And duplicate deliveries (at-least-once) reuse the same event_id to enable consumer idempotency And delivery status and attempts are queryable via GET /v1/webhook-deliveries/{event_id}
Webhook Events for Enforcement Blocks and Override Changes
Given enforcement is enabled for caps on series S When a scheduling action would exceed the cap and is blocked Then an enforcement_block webhook is emitted with fields block_reason, attempted_credits, remaining_credits matching schema timeTether.webhooks.v1.enforcement_block When a cap override on series S is created, updated, or deleted Then an override_change webhook is emitted with fields action=create|update|delete and previous/new values matching schema timeTether.webhooks.v1.override_change And events for the same series and cap are delivered in occurred_at order
OAuth Scopes, Per-Series Permissions, and Rate Limiting
Given access tokens with varying scopes and series permissions When calling any Caps API endpoint without the required scope Then 403 Forbidden is returned with error code insufficient_scope and a WWW-Authenticate header listing required scopes When calling an endpoint for series S without permission to S Then 403 Forbidden is returned with error code forbidden_series When a client exceeds 100 requests per minute Then 429 Too Many Requests is returned with Retry-After and X-RateLimit-* headers And retrying a previously failed create with the same Idempotency-Key after 429 does not produce duplicate side effects

JIT Grants

Grant just-in-time, task-scoped calendar access that auto-expires after a set window or upon series creation. Owners approve in one click; TimeTether logs and revokes automatically. Schedulers get fast, compliant access without lingering permissions.

Requirements

One-Click JIT Access Request & Approval
"As a calendar owner, I want to approve one-time, task-scoped access in one click so that schedulers can complete time-critical booking without granting lingering permissions."
Description

Enable schedulers to trigger a just-in-time access request from the scheduling flow when TimeTether lacks required calendar permissions. The calendar owner receives a contextual, actionable notification (in-app, email, or Slack) showing the task, requested scopes, target calendar, and time window, and can approve or deny in one click. Upon approval, TimeTether provisions short-lived credentials bound to the specified scopes and calendar, stores an audit record, and resumes the scheduling action without the scheduler leaving their workflow. If denied or expired, TimeTether fails closed and offers compliant fallback options (e.g., send availability poll).

Acceptance Criteria
JIT Request Trigger Within Scheduling Flow
Given a scheduler initiates a scheduling action that requires calendar access they do not have When the system detects insufficient permissions during the flow Then a JIT access request prompt is displayed inline without redirecting the user away from the flow And the prompt displays the requested scopes, target calendar, and requested time window derived from the action And the scheduler can submit the request in a single click And if an active JIT grant already satisfies the required scopes and calendar, no new request is created and the flow proceeds And repeated submissions while a request is pending do not create duplicate requests
Owner One-Click Approval From Actionable Notification
Given the calendar owner receives an in-app, email, or Slack notification for a JIT access request When the owner selects Approve in the notification Then the request is approved in a single action using a secure one-time link or in-app control And the notification clearly shows the task context, requested scopes, target calendar, and time window And if the owner selects Deny, the request is immediately marked denied And approval/denial links expire after first use or when the request expires, whichever occurs first
Provision Scoped, Time-Bound Credentials Upon Approval
Given a JIT request is approved When TimeTether provisions credentials Then the credentials are limited to the specified calendar resource and approved scopes only And the credentials expire at the end of the approved time window And attempts to use the credentials outside the scopes or on other calendars are rejected And credentials are stored encrypted at rest and are not visible to the scheduler
Fail Closed With Compliant Fallback on Deny or Expiry
Given a JIT request is denied or expires before completion of the scheduling action When the scheduler attempts to proceed Then the system blocks write operations to the calendar And the UI offers compliant fallback options, including sending a prefilled availability poll, in the same flow And no calendar changes are made And the scheduler is informed of the denial/expiry with a clear reason and timestamp
Auto-Revocation After Series Creation or Window End
Given a JIT grant is active When TimeTether successfully creates the meeting series or completes the scoped task Then the grant is revoked immediately And any subsequent API calls using the revoked credentials fail with an authorization error And if the task is not completed, the grant auto-expires at the end of the approved window without manual action And revocation time and reason are recorded
Audit Log Completeness and Integrity
Given any JIT request lifecycle event occurs (requested, approved, denied, expired, revoked, used) When the event is processed Then an audit record is created with requester identity, owner identity, task identifier, target calendar, requested scopes, time window, timestamps, action, actor, and outcome And audit records are immutable, timestamped to the second, and retrievable by admin search within 5 seconds of the event And audit logs include correlation IDs linking request, approval, provisioning, and revocation events
Seamless Resume of Scheduler Workflow After Approval
Given a scheduling action is blocked pending JIT approval When the owner approves the request Then the scheduler’s action resumes automatically without leaving the flow and completes if no other blockers exist And the system retries the blocked operation within 5 seconds of approval receipt And the UI reflects success or next required action within 2 seconds after completion
Task-Scoped Permission Templates
"As a scheduler, I want clear, pre-scoped access options tied to my task so that I only request what is needed and owners feel safe approving quickly."
Description

Provide predefined, least-privilege permission templates mapped to common scheduling tasks (e.g., free/busy lookup, create series, modify only events created by TimeTether). Each request binds to a specific calendar and operation set, with optional constraints such as series identifier or event namespace. The UI clearly explains what will and will not be accessible, reducing cognitive load and approval friction. The backend translates templates to provider-specific scopes for Google and Microsoft 365, validates compatibility, and enforces scope minimization at token issuance.

Acceptance Criteria
Template Selection and Permission Summary UI
- Given a scheduler selects the "Create Series (TT namespace)" template for Calendar A, When the owner opens the approval modal, Then the modal displays "Calendar: Calendar A" and "Template: Create Series (TT namespace)". - Then the modal lists the allowed operations exactly as defined by the template. - Then the modal lists explicit denials including "No access to other calendars" and any operations not included in the template. - Then any optional constraints (e.g., seriesId, event namespace) are displayed with their values. - Then primary "Approve access" and secondary "Decline" actions are available and enabled. - Then the modal provides a "View provider scopes" details section without leaving the approval flow.
Provider Scope Translation and Minimization
- Given provider = Google Workspace and template = Free/Busy Lookup, When the owner expands "View provider scopes", Then the displayed scopes are the minimal set needed for free/busy and exclude event read/write scopes. - Given provider = Microsoft 365 and template = Create Series (TT namespace), When "View provider scopes" is expanded, Then the scopes exclude mailbox-wide read permissions and do not exceed create/modify own TT events. - Given any template, When mapping cannot achieve least-privilege for the selected provider, Then approval is blocked and the modal shows a compatibility error explaining the unsupported scope with remediation. - Given a successful mapping, When approval is granted, Then the issued token requests exactly the displayed scopes and no additional scopes.
Calendar- and Task-Scoped Token Enforcement
- Given a token issued for Calendar A with operations [Create, ModifyOwnTT], When an API client attempts any operation on Calendar B, Then the request is denied with HTTP 403 and an audit entry is recorded. - Given the same token, When an API client attempts an operation not in [Create, ModifyOwnTT] on Calendar A (e.g., DeleteNonTT, ReadAll), Then the request is denied with HTTP 403. - Given the same token, When creating a series on Calendar A via TimeTether, Then the request succeeds with HTTP 2xx and the event metadata includes the TimeTether namespace.
Series Identifier and Event Namespace Constraints
- Given a token issued with seriesId constraint = SER-123 and namespace = TimeTether, When creating or modifying events without namespace=TimeTether, Then the request is denied with HTTP 403. - Given the same token, When modifying or deleting an event whose seriesId != SER-123, Then the request is denied with HTTP 403. - Given the same token, When creating or modifying an event with namespace=TimeTether and seriesId=SER-123 on the bound calendar, Then the request succeeds with HTTP 2xx.
Least-Privilege: Modify Only TimeTether-Created Events
- Given a token issued from the "Modify Only TT Events" template for Calendar A, When updating a TimeTether-created event on Calendar A, Then the request succeeds with HTTP 2xx. - Given the same token, When attempting to update or delete an event not created by TimeTether on Calendar A, Then the request is denied with HTTP 403. - Given the same token, When attempting to read event details of non-TimeTether events on Calendar A, Then the response contains no data or redacted data as per the template (e.g., only free/busy), and no full event content is returned.
Audit Logging and Approval Traceability
- Given an access request is approved, When the token is issued, Then an immutable audit record is created that includes requester, approver, calendar ID, template name, granted operations, constraints, provider, provider scopes, issuedAt, expiresAt, and a unique grant ID. - Given any API call made with the issued token, When the call completes, Then an audit event is appended linking the call to the grant ID within 60 seconds. - Given the grant auto-expires or is revoked, When that event occurs, Then a revocation audit entry is created and the token can no longer be used (subsequent calls return HTTP 401/403). - Given an admin searches the audit console by grant ID, When results load, Then all related events (issuance, approvals, API calls, revocation) are visible chronologically.
Time-Bound Access & Auto-Expiry
"As a security-conscious admin, I want JIT access to expire automatically by time or completion so that permissions do not linger and reduce risk."
Description

Constrain JIT Grants to a fixed duration (e.g., 30–120 minutes) or automatic termination upon series creation, whichever occurs first. Implement a revocation engine that tracks grant lifecycles, handles clock skew, and proactively revokes credentials before expiry when the task completes. Notify requester and owner on activation and revocation, and surface remaining time in the UI. Ensure resilience with retries, idempotency keys, and dead-letter handling so grants are not left active due to transient provider errors.

Acceptance Criteria
Time-Limited Grant Duration and Enforcement (30–120 Minutes)
Given a scheduler requests a JIT grant with a specified duration between 30 and 120 minutes inclusive When the owner approves the request Then the system creates a grant with an expiry timestamp equal to approval_time + requested_duration And requests with duration outside the 30–120 minute range are rejected with HTTP 422 and a descriptive error And the grant cannot be extended or renewed via client calls; attempts are rejected with HTTP 403 And all access attempts using the grant after the expiry timestamp are denied by TimeTether within 10 seconds
Auto-Revocation on Series Creation (Whichever Occurs First)
Given an active JIT grant tied to a scheduling task When a meeting series is successfully created for the target owner under the scope of the task Then the grant is revoked within 15 seconds of event detection, with status 'revoked' and reason 'series_created' And subsequent access attempts using the grant are denied And multiple series-created events are handled idempotently with no duplicate side effects
Clock Skew Handling and Proactive Revocation Buffer
Given server time is authoritative and clients may have up to ±2 minutes clock skew When a grant is activated Then the revocation engine schedules revocation at expiry_time minus 60 seconds (proactive buffer) And if provider-side propagation delay is detected, TimeTether’s proxy denies access at or before expiry_time regardless of provider state And all system timestamps in logs and notifications are presented in both UTC and the owner’s timezone
Activation and Revocation Notifications to Requester and Owner
Given a JIT grant is activated or revoked When the state changes to 'active' or 'revoked' Then both the requester and the owner receive a notification for each event within 30 seconds And each notification includes grant_id, actor, reason (activation|series_created|time_expired|manual), and expiry_time And notification delivery is retried up to 3 times on transient failures; undelivered notifications are logged with error status
UI Remaining-Time Display and Sync with Server Time
Given a user (requester or owner) views the grant details UI while a grant is active When the page loads and every 15 seconds thereafter Then the remaining time is displayed as a countdown synchronized to server time with maximum drift of 2 seconds And the UI shows 'Expires on <date/time>' and a live 'MM:SS' countdown And at expiry, the UI updates to 'Expired' within 5 seconds and disables any actions requiring the grant
Retries, Idempotency, and Dead-Letter Handling for Provider Errors
Given any create, refresh, or revoke call to an external calendar provider When a transient error (HTTP 5xx, network timeout) occurs Then the call is retried with exponential backoff up to 5 attempts and a maximum elapsed time of 2 minutes And all mutating requests include an idempotency key that guarantees at-least-once semantics without duplicate side effects on replay within 24 hours And if retries exhaust, the operation is placed on a dead-letter queue with alerting, and the grant is locally disabled (access denied at proxy) until successful provider revocation completes
Audit Logging and Lifecycle Traceability
Given any grant lifecycle event (requested, approved, activated, revoked, expired, failed_revocation) When the event occurs Then an immutable audit log entry is written with fields: grant_id, event_type, timestamp (UTC), requester_id, owner_id, reason, and correlation_id And audit entries are queryable by grant_id within 2 seconds of event time And audit logs are retained for at least 365 days
Audit Logging & Compliance Trail
"As a compliance officer, I want a complete audit trail for each grant so that we can demonstrate least-privilege and timely revocation during audits."
Description

Capture an immutable, queryable audit trail for every grant including requester, owner, task context, scopes, target calendar, approval channel, timestamps, IP/device signals, and revocation outcome. Expose in-product filters and export (CSV/JSON) and webhook streaming for SIEM integration. Link audit entries to the resulting meeting series or events in TimeTether for end-to-end traceability. Store logs in append-only storage with retention policies aligned to SOC 2/ISO 27001 requirements.

Acceptance Criteria
Log Capture on JIT Grant Approval
Given a scheduler submits a JIT grant request with task context, scopes, and target calendar, and the calendar owner approves via in-app, email, or Slack When the approval action is processed Then exactly one audit entry is created for the approval with fields: entry_id, grant_id, requester_id, owner_id, task_context_id, scopes[], target_calendar_id, approval_channel, approval_timestamp (ISO 8601 UTC), requester_ip, owner_ip, requester_device_fingerprint, owner_device_fingerprint And the audit entry is durably persisted before any access token/permission is issued And p95 latency from approval to persistence is <= 200 ms And if persistence fails, the grant is not activated and the system returns a clear error and retries up to 3 times with backoff
Append-Only Immutable Storage with Tamper Evidence
Given audit logging storage is initialized When any audit entry is written Then the entry is stored in append-only WORM storage with retention lock equal to the organization's policy (minimum 1 year; configurable to align with SOC 2/ISO 27001) And each entry includes prev_hash and entry_hash (SHA-256) forming a verifiable hash chain And update or delete operations are blocked until retention expiry and any attempt is logged as a security event And a daily integrity job verifies 100% chain continuity and emits an alert if continuity < 100% And upon retention expiry, entries are purged only via a scheduled job, producing a signed purge manifest that is itself logged and exportable
In-Product Audit Log Filtering and Query
Given a compliance admin opens the Audit Logs UI When they apply filters by time range, requester, owner, task context, scope, target calendar, approval channel, revocation outcome, and IP subnet, and sort by timestamp Then the result set reflects all applied filters (logical AND) and sorting And the UI paginates results with page sizes 25/100/500 and indicates total count And p95 query response time is <= 2 seconds for up to 100k matching entries And selecting a row reveals full field detail and raw JSON view
CSV and JSON Export of Audit Logs
Given any current filter is applied in the Audit Logs UI When the admin exports as CSV or JSON Then the export includes only the filtered entries and all fields with a consistent schema_version And timestamps are ISO 8601 UTC, encoding is UTF-8, lists are comma-separated in CSV and arrays in JSON And exports up to 5,000,000 rows complete within 5 minutes via streaming download with resume support And the generated download link is single-use and expires in 15 minutes And the export action itself is recorded as an audit entry
Webhook Streaming for SIEM
Given a tenant has configured a SIEM webhook endpoint with a shared secret When audit events occur (grant_requested, grant_approved, grant_denied, grant_revoked, grant_auto_expired, series_created_linked, export_performed, integrity_alert) Then a POST is sent within 5 seconds containing event_id, occurred_at (ISO 8601 UTC), event_type, payload, idempotency_key, schema_version, and an HMAC-SHA256 signature header And deliveries are at-least-once with per-grant ordering preserved And failures trigger exponential backoff retries for up to 24 hours with a retry dashboard showing status And a 2xx response marks the delivery as successful; non-2xx retains for retry
End-to-End Traceability to Meeting Series and Events
Given a JIT grant is used to create a meeting series or events in TimeTether When the series or events are created Then the originating audit entries include series_id and/or event_ids, and the series/events reference grant_id and audit_entry_id And the UI shows deep links from the grant audit entry to the series/events and vice versa And if no series/events are created before grant expiry, the revocation audit entry cites the task context and reason "no_series_created"
Auto-Revocation and Outcome Logging
Given a JIT grant with an expiry window or a trigger to revoke upon series creation When the expiry time is reached or the series is created Then access is revoked within 60 seconds p95 And a revocation audit entry is appended with outcome (success|partial|failure), timestamp, revoker (system|user), affected scopes, and error_details (if any) And failed revocations are retried every 5 minutes for 24 hours with alerts to admins And after a success, permission verification confirms no lingering scopes on the target calendar
Automatic Revocation & Scope Rollback
"As an IT admin, I want revocation to be automatic and reliable so that we do not depend on users to remove access manually."
Description

On expiry or task completion, revoke provider tokens and remove any temporary calendar ACLs or event-level permissions granted for the JIT window. Implement provider-specific rollback paths for Google and Microsoft 365 with exponential backoff, rate-limit awareness, and compensating actions to ensure the calendar returns to its prior state. Display revocation status in the UI and mark the grant closed only after successful confirmation, with alerts for any grants requiring admin attention.

Acceptance Criteria
Trigger-Based Token and ACL Revocation (Expiry, Task Completion, Series Creation)
Given a JIT grant for a scheduler on Google or Microsoft 365 with temporary calendar ACLs and/or event-level permissions And a baseline ACL snapshot is recorded at grant approval When the grant expires OR the scheduling task completes via confirmed series creation Then provider access tokens for the grant are revoked at the provider And any temporary calendar ACL entries created by the grant are removed And any event-level permissions created by the grant are removed (including series and individual occurrences) And the post-rollback ACLs equal the baseline snapshot And the revocation completes within 2 minutes of the trigger
Provider-Specific Rollback Paths and Exponential Backoff
Given revocation steps against Google Calendar/Workspace return a transient error (5xx or 429) When retrying Then exponential backoff with jitter is applied with a maximum of 5 attempts within 5 minutes And any Retry-After header is honored before the next attempt Given revocation steps against Microsoft Graph return 429 or 503 When retrying Then exponential backoff with jitter is applied with a maximum of 5 attempts within 5 minutes And any Retry-After header is honored before the next attempt Given a non-transient 4xx error (excluding 429) occurs Then retries stop and the grant is marked Attention Required
Rate-Limit Awareness and Quota Safety
Given multiple revocations are queued concurrently When processing provider API calls Then calls are paced so configured per-tenant and global rate limits are not exceeded And upon receiving a 429 with Retry-After, no further calls for that provider scope are issued until the interval elapses And monitoring metrics show zero additional 429s for the same scope during the enforced backoff window
Compensating Actions and State Restoration to Prior Baseline
Given an ACL/event permission removal returns 404 or resource-already-deleted Then the step is treated as idempotent success and processing continues Given the final ACL/permission diff against the baseline is non-empty after primary rollback When compensating actions run Then orphaned events, ACLs, and subscriptions from the JIT window are removed or reassigned per provider capabilities And the final ACL and permission state matches the baseline within 5 minutes or the grant is flagged Attention Required
UI Revocation Status and Grant Closure Rules
Given a grant enters revocation Then the UI displays status "Revoking" with time since trigger Given all provider confirmations succeed Then the UI switches to "Revoked" and the grant is marked Closed Given any step fails after max retries or exceeds 5 minutes total duration Then the UI displays "Attention Required" with last error and recommended next action Then Closed status is permitted only after a provider confirmation receipt is stored for each rollback action performed
Admin Notifications and Remediation Controls
Given a grant is in Attention Required Then an alert is sent to designated admins via email and in-app within 1 minute And the alert includes provider, tenant, grant ID, failing actions, last error, and a one-click "Retry Revocation" control When an admin triggers "Retry Revocation" Then rollback restarts using idempotent operations and updated backoff windows And the alert auto-resolves when revocation completes successfully
Audit Logging, Token Sanitization, and Idempotency Guarantees
Given any revocation action runs Then an immutable audit log entry is recorded with timestamp, actor=system, provider, scope, action, result, and correlation ID And provider tokens for the grant are revoked at the provider and securely deleted from storage And subsequent attempts to use the tokens result in unauthorized and are logged And re-running the revocation workflow makes no additional provider-side changes for already-removed resources and records no-op results And audit logs are queryable by grant ID and exportable within 30 seconds of action completion
Admin Policies & Guardrails
"As an org admin, I want configurable guardrails for JIT Grants so that access remains consistent with our security policies across teams."
Description

Allow organization admins to define maximum grant durations, permitted templates/scopes, approval requirements, and allowed request/approval channels. Provide defaults at org and team levels with the ability to enforce strict policies for high-risk calendars. Detect and alert on anomalous patterns (e.g., repeated elevated-scope requests) and block non-compliant requests at creation time. Integrate with SSO/SCIM to respect roles and ownership, ensuring only verified owners can approve grants for their calendars.

Acceptance Criteria
Enforce Maximum Grant Duration with Org/Team/Calendar Defaults
- Given org policy maxDuration=24h and team "Platform" maxDuration=8h, When a scheduler requests 10h for any "Platform" calendar, Then the request is blocked as non-compliant with error code POL-DUR-001 and an audit entry is created with policyRef and requesterId. - Given org policy maxDuration=24h and no team override, When a scheduler requests 24h, Then the request is allowed and the grant expiration is set to 24h from approval. - Given calendar "Security-Exec" marked high-risk with maxDuration=2h, When a scheduler requests 3h, Then the request is blocked regardless of org/team settings and an admin notification is sent.
Restrict Permitted Templates and Scopes
- Given org policy permits scopes=[freebusy.read, events.create.series] and templates=["quarterly-pi-planning","sprint-demo"], When a request includes scope events.read.all, Then the request is blocked with code POL-SCP-001 and the attempted scope is recorded in audit. - Given team "Platform" policy removes template "sprint-demo", When a "Platform" requester selects "sprint-demo", Then the request is blocked with code POL-TPL-001 and the UI/API returns the list of permitted templates. - Given a request uses only permitted scopes and a permitted template, When created, Then the request is allowed and the exact granted scopes and templateId are persisted for audit and expiry enforcement.
Verify Approver via SSO/SCIM Ownership
- Given SSO/SCIM indicates user A is owner of calendar C and user B is not, When B clicks approve, Then the approval is rejected with code POL-OWN-001 and an alert is logged with approverId and calendarId. - Given A clicks one-click approve, When the grant is created, Then approverId matches A, the grant expiry honors the applicable policy, and the audit entry includes SSO subject, approver role, and timestamp. - Given SCIM shows A is a delegated approver for C, When A approves, Then approval succeeds; When SCIM revokes the delegation, Then A’s subsequent approvals are rejected within 5 minutes of the revocation sync.
Enforce Allowed Request and Approval Channels
- Given org policy allows channels=[Slack, Web] for requests and approvals, When a request is initiated via Email link, Then the request is blocked with code POL-CHN-001 and no grant is created. - Given team "Security" policy disallows Slack approvals, When an approval is attempted in Slack for a "Security" calendar, Then the action is blocked and the approver is prompted to use the Web channel. - Given a request/approval occurs via an allowed channel, Then it is processed and the channel is recorded in the audit log and metrics.
Detect and Alert on Anomalous Elevated-Scope Patterns
- Given anomaly rule R1: threshold=3 elevated-scope requests per 7 days per requester, When user U submits the 3rd elevated-scope request within 7 days, Then the event is flagged, org admins are notified via the configured channel, and anomalyId is appended to the request audit. - Given policy autoBlockOnAnomaly=true, When a request is flagged by R1, Then the request is blocked pre-creation with code POL-ANM-001; When autoBlockOnAnomaly=false, Then the request is created but marked pending review and requires owner approval even if policy would normally auto-approve. - Given no anomaly is detected for U, Then no anomaly alert is generated and the request proceeds per standard policy.
Block Non-Compliant Requests at Creation Time with Clear Errors
- Given any policy violation (duration, scope, channel, ownership), When the request is submitted, Then no grant is created and the API returns HTTP 403 with a machine-readable code and human-readable message naming the violated rule(s). - Given multiple violations, When submitted, Then the response enumerates all violated ruleIds and remediation hints; the audit log contains a full policy evaluation summary with timestamps and evaluator version. - Given a compliant request, When submitted, Then it is created within p95 <= 300ms and includes policyEvaluationId in the audit trail.

Scope Presets

Role-based, least-privilege scope templates with default expirations and calendar subsets. Admins apply consistent, minimal access in seconds, reducing setup time and mis-scoped permissions.

Requirements

Role-Based Scope Template Catalog
"As an org admin, I want ready-to-use, least-privilege templates by role so that I can apply consistent, minimal access quickly and avoid permission sprawl."
Description

Provide a curated library of least-privilege scope templates aligned to common TimeTether roles (e.g., Org Admin, Team Lead, Scheduler, Read-only). Each template encapsulates provider-specific OAuth scopes (Google Workspace, Microsoft 365), default access expiration, and optional calendar subset rules. Include full CRUD (create, clone, edit, archive, delete), import/export as JSON, preview of requested permissions, and RBAC so only authorized admins can publish or apply templates. Store templates in the configuration service with environment awareness (dev/staging/prod) and enable localization of template names and descriptions. This delivers consistent, minimal access in seconds, reduces mis-scoped permissions, and standardizes setup across distributed teams.

Acceptance Criteria
Create and Publish Role-Based Scope Template
Given I am an authorized admin with TemplateEditor and TemplatePublisher permissions in environment "staging" When I create a new template for role "Team Lead" with provider scopes for Google Workspace and Microsoft 365, set default access expiration to 90 days, and add valid calendar subset rules, then save as Draft Then the template is persisted as Draft v1 in the configuration service scoped to "staging" with role key, provider scopes, expiration policy, and calendar subsets recorded And provider scope identifiers and calendar IDs are validated against supported providers; invalid values block save with field-level errors When I publish the Draft Then the template becomes Published v1 with immutable contents, publisher ID, timestamp, and is visible in the catalog to authorized users in "staging" When I apply the Published template to an integration Then the OAuth consent requests only the template’s scopes, the access expiration defaults to 90 days from apply time (not exceeding org maximum), and any calendar subset rules are enforced in the resulting policy
Clone and Edit Template Without Altering Published Version
Given a Published template v1 exists in environment "prod" When I clone the template Then a new Draft v2 is created with lineage reference to v1 and a new Draft ID When I edit scopes, expiration, or calendar subsets in v2 and save Then v1 remains unchanged and v2 reflects the edits When I publish v2 Then v2 becomes the current Published version and v1 remains read-only and accessible for audit/history
Archive, Restore, and Delete Template with Audit Trail
Given a Published template exists When I archive the template Then the template state becomes Archived, it is no longer selectable for apply, and an audit record is created with actor, timestamp, template ID, prior state, and environment When I restore the template Then the state returns to Published and an audit record is created for the restore action When I attempt to delete a Draft template Then the Draft is permanently removed with an audit record; Published or Archived versions cannot be deleted and the system returns a 409 with an explanatory message
Import and Export Templates as JSON with Schema Validation
Given I have a template JSON matching schemaVersion >= the system’s minimum When I import the JSON into environment "dev" Then schema validation, provider scope keys, and calendar ID formats are validated; on success a Draft is created with a new ID unless overwrite=true and a Draft with the same ID exists When I export a Draft or Published template Then the JSON includes id, roleKey, environment, status, version, schemaVersion, providerScopes, expirationPolicy, calendarSubsets, localizedTexts, createdBy/updatedBy, and timestamps When I import invalid JSON Then the import is rejected with a 422 response containing a list of field-specific errors and no templates are created or modified
Permission Preview of OAuth Scopes and Calendar Subsets
Given a Draft or Published template exists When I open the permission preview Then all requested OAuth scopes are listed grouped by provider with human-readable labels and provider keys, and a total scope count is displayed And any scope flagged as high-privilege is indicated with a warning badge And calendar subset rules are displayed with calendar names and IDs And the preview content exactly reflects the template’s stored data and the export JSON
RBAC Enforcement for Publishing and Applying Templates
Given a user lacks TemplatePublisher permission When they attempt to publish or apply a template Then the request is denied with HTTP 403 and no state changes occur Given a user has TemplateEditor but not TemplatePublisher When they create, clone, edit, and save templates as Draft Then the actions succeed Given a user has TemplateViewer When they browse the catalog, view details, preview permissions, and export Then the actions succeed while create/edit/publish/apply controls are disabled And all RBAC checks are enforced server-side and recorded in the audit log
Environment-Aware and Localized Templates with Fallbacks
Given I am operating in environment "staging" When I list templates Then only templates from "staging" are returned and counts exclude other environments And template IDs are unique per environment Given a template has localized name and description for en-US and fr-FR When my locale is fr-CA Then the UI returns fr-FR values; if unavailable, it falls back to en-US without error and logs a missing-translation notice
Least-Privilege Scope Resolver
"As a security-conscious admin, I want the system to automatically select the smallest necessary scopes so that users get only the access required to run TimeTether without overexposure."
Description

Implement a policy engine that maps TimeTether capabilities (read free/busy, send invites, manage recurring meetings) to the smallest set of provider OAuth scopes per vendor, with cross-provider normalization. On template design and apply, the resolver computes the minimal scope set, flags over-broad selections, and blocks noncompliant combinations. Provide a preflight simulator that shows which actions are enabled by the current scope set and a compatibility matrix for Google Workspace and Microsoft 365. Include regression tests to prevent scope creep, and a migration assistant to suggest narrower alternatives when providers deprecate or split scopes. This ensures templates stay least-privilege while preserving required product functionality.

Acceptance Criteria
Minimal Scope Resolution for Capability Set
Given a template with capabilities: read free/busy, send invites, and manage recurring meetings When the resolver computes the OAuth scopes for Google Workspace and Microsoft 365 Then it returns, per provider, the minimal set of scopes required to perform only the selected capabilities And removing any single scope from the returned set causes at least one selected capability to fail in an integration test And the returned set contains no calendar read or write scopes if read free/busy is the only read capability selected And the computation completes within 300 ms per provider And the resolved scopes and rationale are recorded in an audit log entry tied to the template ID
Cross-Provider Capability Normalization
Given resolved provider scopes for Google Workspace and Microsoft 365 for the same selected capabilities When the normalization maps scopes to internal capabilities Then the normalized capability set is identical across providers for the same selected capabilities And any provider-specific overreach maps to zero additional normalized capabilities and is flagged as over-broad And the normalized set exactly matches the user-selected capabilities (no missing, no extras) And a diff view shows any mismatch and prevents save until resolved
Over-Broad Scope Detection and Blocking
Given an admin proposes or edits a template with scopes that exceed the computed minimal set When the admin attempts to save or apply the template Then the system blocks the action with an error listing each over-broad scope and its narrower alternative And offers a one-click Replace All to substitute narrower scopes And prevents issuance of OAuth consent flows that include blocked scopes And records the block event, offending scopes, and user ID in an audit log
Preflight Simulator Action Visibility
Given a current scope set for a template and a selected provider When the preflight simulator is run Then it lists all enabled actions (read free/busy, send invites, manage recurring meetings) and all disabled actions with reasons And for each enabled action, it shows the specific scope(s) that permit it And for each disabled action, it shows the minimal additional scope(s) needed And the simulator output is exportable as JSON and matches capability checks in a dry-run test suite with zero false positives/negatives And the simulator responds within 400 ms for scope sets of 10 scopes or fewer
Compatibility Matrix for Google and Microsoft
Given the latest provider scope catalogs and the internal capability map When the compatibility matrix is generated Then it presents a matrix of capabilities versus provider scopes with statuses: supported, requires extra consent, not supported And includes last-updated timestamps and provider documentation references for each mapping And allows filtering by capability and provider And passes accessibility checks to WCAG 2.1 AA for tables And is available via API endpoint /scopes/matrix with ETag caching
Regression Guardrails Against Scope Creep
Given a baseline of minimal scope sets stored in version control When the resolver output changes compared to the baseline for any capability set Then CI fails with a diff highlighting newly added or broadened scopes And merging requires an approved override from Security code owners And a new baseline is accepted only with updated tests demonstrating necessity And historical baselines remain queryable for at least 12 months
Migration Assistant for Provider Scope Changes
Given a provider deprecates or splits a scope used by any template When the daily provider discovery check runs Then affected templates are identified and suggested narrower replacement scopes are computed And admins receive an in-app notification and email with a one-click migration preview And running the migration updates templates without increasing normalized capabilities and passes preflight simulator checks And a rollback option restores previous scopes if API calls fail during migration And all migrations are logged with before/after scope sets and timestamps
Calendar Subset Filters and Dynamic Resolution
"As a compliance admin, I want to restrict templates to specific calendar subsets so that access is limited to relevant team resources and automatically updates when teams change."
Description

Enable templates to define which calendars are in scope using flexible filters (owner is assignee, team or group tags, resource calendars only, domain restrictions, name keywords). On assignment, dynamically resolve the allowed calendar set per user via provider APIs and directory groups, refreshing periodically to reflect org changes. Provide conflict handling when calendars move or permissions change, and allow fallback rules (e.g., exclude personal calendars). Expose a preview of the resolved calendar list prior to apply. This tightly constrains access to only relevant calendars while adapting automatically as teams evolve.

Acceptance Criteria
Owner-Is-Assignee Filter Resolution
Given a scope template with filter "owner is assignee" and a connected calendar provider When the template is assigned to user U and dynamic resolution runs Then the resolved set includes only calendars where ownerId == U And shared calendars not owned by U are excluded And resolution uses provider read-only APIs and completes within 5 seconds for users with up to 200 calendars
Team/Group Tag Filter via Directory Groups
Given a scope template with team/group tag filter "Platform" And directory integration mapping users to groups is enabled And calendars tagged team=Platform exist, and others are tagged team!=Platform When the template is assigned to a user who is in the Platform group Then only calendars tagged team=Platform are included and others excluded And group membership is fetched from the directory at resolution time and results are de-duplicated
Resource Calendars Only Enforcement
Given a scope template with filter "resource calendars only" When the template is assigned and resolution runs Then the resolved set contains only calendars where type == resource (e.g., rooms, equipment) And personal and group calendars are excluded regardless of ownership or tags
Domain and Keyword Filter Combination
Given a scope template with domain restrictions ["example.com"] and name keywords ["oncall","release"] And the rule logic is AND across categories and OR within a category When dynamic resolution runs Then each included calendar has primaryEmail domain in {example.com} AND name contains any of {oncall, release} (case-insensitive) And calendars failing either condition are excluded
Preview of Resolved Calendars Prior to Apply
Given a scope template and a selected assignee When the admin clicks "Preview" Then the UI displays the exact list that would be granted (fields: Name, Owner, Type, Domain, Inclusion Filters, Last Refreshed) And the total count is shown and matches the number applied upon "Apply" 1:1 And the preview returns within 3 seconds for up to 1,000 calendars
Periodic Refresh Reflects Org Changes
Given an active assignment with a 24h refresh interval And the assignee leaves group A and a new resource calendar matching filters is created When the scheduled refresh runs or an admin clicks "Refresh now" Then calendars accessible only via group A are removed and the new matching resource calendar is added And an audit log records before/after counts and item-level changes
Conflict Handling and Fallback Rules
Given a template with fallback rule "exclude personal calendars" And a previously included calendar loses access or is moved to a different domain When resolution runs (scheduled or manual) Then the inaccessible/moved calendar is removed from the resolved set without expanding access beyond filters And personal calendars are excluded by fallback even if other filters would include them And if the resolved set becomes empty, the assignment remains active with zero calendars and an alert is surfaced to the admin
Default Expiration Policies and Auto-Revocation
"As an IT administrator, I want access granted by templates to expire automatically so that we minimize standing permissions and meet audit requirements without manual cleanup."
Description

Allow each template to specify default access lifetimes (e.g., 90 days) with configurable grace periods, renewal windows, and mandatory re-consent on extension. Schedule automatic revocation of scopes and cleanup of tokens upon expiry across providers, with notifications to assignees and admins prior to expiration. Log all expirations and revocations for auditability and provide API endpoints to extend, revoke, or pause expiration timers in bulk. This reduces lingering access, enforces least-privilege over time, and simplifies periodic access reviews.

Acceptance Criteria
Apply Default Expiration on New Scope Assignments
Given a Scope Preset template with a default access lifetime is active When the template is assigned to a user or group Then the assignment expiry is set to assignment_created_at + default_lifetime and stored in UTC And the expiry is visible in the admin UI and returned by GET /assignments including assignment_id, template_id, and expires_at And changing the template’s default later does not alter existing assignments unless an explicit Re-apply Defaults action is executed
Auto-Revocation and Token Cleanup on Expiry
Given an assignment has reached its expiry and is not in a grace period or paused When the expiration processor runs Then provider scopes are revoked across all connected providers for that assignment And associated tokens and calendar subscriptions for that assignment are removed And the assignment state transitions to revoked and the user loses access to protected actions And per-provider failures are retried and recorded as pending-revocation until resolved
Renewal Window with Mandatory Re-Consent
Given a template has a configured renewal window and mandatory re-consent enabled When an admin or the assignee attempts to extend an assignment within the renewal window Then the extension is blocked until the assignee explicitly re-consents via UI or secure link And upon consent, the new expiry is set to max(current_expiry, now) + approved_duration (defaulting to the template’s default_lifetime) And an audit event extended is recorded with actor, old_expiry, new_expiry, consent_timestamp, and consent_method
Pre-Expiration Notifications to Assignees and Admins
Given pre-expiration notification offsets are configured on the template When an assignment approaches expiry by a configured offset or enters a grace period Then assignees and admins receive notifications containing assignment_id, template_id, expires_at, grace_period, and a renewal/re-consent link And delivery outcomes (sent, bounced, failed) are captured and failed deliveries raise an admin alert
Audit Log of Expirations and Revocations
Given an assignment is expired, auto-revoked, manually revoked, extended, paused, or resumed When the event occurs Then an immutable audit record is written with timestamp (UTC), actor (user_id/service), assignment_id, template_id, event_type, reason, old_expiry, new_expiry, and per-provider result codes And GET /audit?assignment_id={id} returns the ordered events and is filterable by event_type and date range
Bulk API to Extend, Revoke, and Pause
Given a tenant admin with the appropriate permission When they call POST /assignments/bulk/extend|revoke|pause with a list of assignment_ids and an Idempotency-Key Then the API validates authorization and payload, accepts at least 500 IDs per request, and enqueues a job returning 202 with job_id And GET /jobs/{job_id} returns per-item status (success, failed, skipped) and error codes And repeated requests with the same Idempotency-Key and payload within 24 hours do not duplicate effects
Grace Period and Pause Timer Semantics
Given a template defines a grace period duration When an assignment reaches its expiry Then access remains active during the grace period and the assignment status is in_grace And at grace period end, auto-revocation executes per the revocation criteria And when an assignment is paused, the expiration countdown is suspended; no revocation or notifications occur until resumed; upon resume, the expiry is extended by the paused duration
One-Click Apply and Bulk Assignment
"As a platform admin, I want to apply a scope preset to hundreds of users in one step so that onboarding and permission corrections are fast and consistent."
Description

Deliver a streamlined UI and API to apply a template to individuals, groups, or organizational units with a single action. Generate provider-specific consent flows automatically, batch invitations, and track progress with retries, partial failure handling, and rollback on error. Provide a diff view showing the changes that will occur to a user’s current scopes and calendar access, plus safeguards to avoid downgrading essential access unintentionally. Integrate with directory and SCIM for group targeting and with Slack/email for user prompts. This cuts setup time dramatically and ensures consistent application at scale.

Acceptance Criteria
One-Click Apply to Individual with Diff Confirmation
Given an admin with Org Admin role selects a single user and a Scope Preset When the admin reviews the diff and clicks Apply with confirmation Then the preset is applied server-side within 5 seconds (p95) And default expiration defined by the preset is set on all applied scopes And calendar subset access is updated to match the preset And provider-specific consent flows are triggered only when required by provider policy And an audit log entry is recorded with actor_id, subject_user_id, preset_id, diff_hash, and timestamp And the operation is idempotent for 24 hours via Idempotency-Key; repeated Apply with the same inputs makes no additional changes
Bulk Assignment to SCIM Group with Progress Tracking
Given an admin selects a SCIM-synced group of N members (1 ≤ N ≤ 5000) When the admin clicks Apply Then a bulk job is created with per-user tasks and a unique job_id And the UI displays real-time counts for Pending, In-Progress, Succeeded, Failed and updates at least every 5 seconds And invitations/consent prompts are sent to applicable users within 2 minutes of job start And for N ≤ 500, p95 job processing time (excluding user consent wait) is ≤ 10 minutes And a final summary with totals, error codes, and CSV export is available at job completion
Partial Failure Handling with Retries and Rollback
Given a bulk job encounters transient provider errors (e.g., rate limit, network) When a per-user task fails Then it is retried up to 3 times with exponential backoff capped at 10 minutes And tasks failing all retries are marked Failed with machine-readable error codes And any user left in a partial state is automatically rolled back to their pre-job scopes and calendar access within 60 seconds And successfully completed user changes are not rolled back And a Rerun Failed button is available to retry only Failed tasks
Safeguard Against Downgrading Essential Access
Given the organization has policies defining Essential scopes/calendar access When the diff includes removal or expiration-shortening of any Essential item Then the UI blocks Apply and shows a warning listing affected items and counts And Apply is enabled only if the admin selects Override and enters a justification of at least 15 characters And the backend enforces the same policy for API calls, denying without an override flag and reason And audit logs include override_used=true and override_reason And no Essential access is removed when an override is not provided
Provider-Specific Consent and User Notifications
Given some users require provider consent to apply changes When a job starts Then provider-specific consent links are generated per user/provider and expire in 7 days And Slack DM is sent if the user has a mapped Slack ID; otherwise email is sent And notifications are delivered within 2 minutes of link generation And non-responders receive up to 2 reminders at 24h and 72h And upon consent via the link, the user’s task transitions to Succeeded within 30 seconds
Idempotent Apply API
Given an API client with admin privileges When it POSTs to /v1/scope-presets/apply with target_type, target_ids, preset_id, and Idempotency-Key header Then the API responds 202 Accepted with job_id and a status URL And repeating the same request with the same Idempotency-Key returns the same job without duplicating work And sending dry_run=true returns 200 OK with a diff summary and performs no changes And rate limits of 10 requests/minute/org are enforced with 429 on excess And OpenAPI documentation includes request/response schemas and error codes
Diff View Accuracy and Safety Preview
Given an admin opens the Diff view for a selected preset and target(s) When the diff is rendered Then adds, removes, and expirations are displayed in separate sections with item counts And unchanged items are omitted And bulk targets show an aggregate summary and a sample of 50 users, with CSV export of the full diff available And each item shows scope name, provider, and description on expand And post-apply reconciliation confirms the applied changes match the displayed diff for ≥ 99.9% of items (monthly), with discrepancies flagged in telemetry
Template Versioning, Audit Logs, and Drift Detection
"As a compliance officer, I want version history and drift detection for templates so that I can prove control over access and quickly correct deviations."
Description

Version templates with immutable histories capturing who changed what and when, including scope diffs, calendar filter changes, and expiration policy updates. Allow comparison and rollback to prior versions. Continuously detect drift between assigned user permissions and the current template definition, notify admins of discrepancies, and optionally auto-remediate to restore compliance. Provide exportable audit reports to support SOC 2 and ISO 27001 evidence requests. This ensures traceability, compliance, and alignment between intended and actual access.

Acceptance Criteria
Immutable Template Versioning Metadata
- Given an admin updates a Scope Preset template, When they save, Then the system creates a new immutable version with a sequential integer version number, template ID, editor user ID and role, and savedAt timestamp in UTC ISO 8601. - And the version includes a machine-readable change set enumerating each changed field path with old and new values. - Then prior versions are read-only via UI and API; modify/delete attempts return 403 and are audit-logged with actor and reason. - And version creation via UI and API yields identical metadata and content hashes.
Scope, Calendar Filter, and Expiration Diff Visualization
- Given two template versions are selected, When viewing their differences, Then the UI and API present: added/removed permission scopes as explicit lists; calendar filter include/exclude changes; and expiration policy changes (duration, renewal, grace) with old→new values. - And the diff summarizes total additions, removals, and modifications counts. - And the diff is exportable as JSON from the API and viewable as a human-readable panel in the UI.
Version Comparison and Rollback Behavior
- Given an Admin selects prior version Vn of a template, When they initiate Rollback, Then the system creates a new version Vn+1 whose content exactly matches Vn and whose provenance references Vn (reason=rollback, actor, timestamp). - And no historical versions are deleted or altered; attempts are blocked with 403 and audit-logged. - And if "auto-apply to assignments" is enabled, all linked user permissions are synchronized to Vn+1 within 10 minutes and a remediation event is recorded per user. - And rollback is prevented with a clear error if it would violate org guardrails (e.g., exceeds allowed scopes); no partial updates occur.
Continuous Drift Detection and Admin Notifications
- Given users are assigned permissions via a template, When any user’s effective permissions differ from the current template definition, Then a drift event is recorded within 5 minutes with user ID, template ID, diff summary, and detectedAt (UTC ISO 8601). - And admins receive consolidated email and in-app notifications within 10 minutes including impacted user count, templates affected, and links to details. - And duplicate notifications for the same template are suppressed within a 15-minute window and merged into a single thread/event.
Optional Auto-Remediation with Safeguards
- Given auto-remediation is enabled for a template, When drift is detected, Then the system restores affected users to match the template within 10 minutes and records a remediation event with before/after snapshots, actor=system, and outcome=success. - And if remediation fails, the system retries up to 3 times with exponential backoff and posts a failure notice including error codes and a one-click manual fix link. - And if "require approval" is enabled, remediation is queued and no changes are applied until an Admin approves; upon approval, the same timing and logging rules apply. - And templates can define an exceptions list; users on it are excluded from auto-remediation but still generate drift events.
Audit Log Integrity, Retention, and Access Control
- Given any versioning, rollback, drift detection, notification, or remediation action occurs, When it is logged, Then the audit entry includes action, actor (user/system), resource IDs, request ID, timestamp (UTC ISO 8601), and before/after snapshots where applicable. - And audit storage is append-only and tamper-evident via chained hashes; verification of the chain succeeds for any contiguous export. - And audit logs are retained for at least 400 days and are queryable by time range, actor, template ID, and action. - And only Admin and Auditor roles can view/export audit logs; access attempts are permission-checked and themselves audit-logged.
Exportable SOC 2 / ISO 27001 Audit Reports
- Given an Admin or Auditor provides a date range and template selection, When they request an export, Then the system generates downloadable CSV and JSON files plus a PDF summary within 2 minutes for up to 100,000 events. - And exports include: org ID, template ID, version IDs, action types, timestamps, actors, diffs, drift incidents, remediation outcomes, and checksums. - And exported files are available via a signed URL that expires within 24 hours; filenames include generation timestamp and content hash. - And large exports are chunked/paginated and are resumable; partial exports are clearly labeled and can be continued without data loss.
Policy Guardrails and Approval Workflow
"As a security manager, I want enforced guardrails and approvals for elevated templates so that risky permissions cannot be applied without oversight."
Description

Introduce organization-wide guardrails that enforce maximum scope levels, mandatory expirations, forbidden scopes, and required calendar subset constraints. When a template exceeds policy thresholds or requests elevated scopes, route it through a configurable approval workflow with designated approvers, justification fields, and time-limited approvals. Block publication and application of noncompliant templates and capture complete evidence for audits. Integrate notifications via Slack/email and expose policy evaluation results in the UI for transparency. This prevents mis-scoped templates and aligns access with organizational risk posture.

Acceptance Criteria
Guardrails: Max Scope Level and Forbidden Scopes Enforcement
Given an admin creates or edits a scope template And an organization policy defines maximum allowed scope per role and a list of forbidden scopes When the template includes any scope exceeding the maximum or includes a forbidden scope Then the system blocks saving and publishing the template And the UI displays each violated rule with rule ID, name, and a remediation hint And the template remains in Draft state and cannot be applied to users And no invites are sent And an audit event is recorded with user ID, policy version, violated rule IDs, and timestamp
Mandatory Expiration Compliance
Given an organization policy mandates expiration on all templates with an allowed range and a default expiration When an admin creates a template without specifying an expiration Then the system auto-populates the default expiration and surfaces it in the UI and API When an admin sets an expiration outside the allowed range Then the system blocks publishing and displays the allowed range to the user And the policy evaluation marks the expiration rule as Fail until corrected
Required Calendar Subset Constraints
Given a policy defines allowed calendar subsets per role/team When a template includes calendars outside the allowed subset for its role/team Then the system blocks publishing and identifies the specific calendars that violate policy And suggests allowed subsets to the admin When all selected calendars are within the allowed subset Then the policy evaluation marks the calendar subset rule as Pass
Approval Workflow for Elevated Scopes
Given a configurable approval workflow with designated approvers and required requester justification for elevated scopes And a policy that allows approval as an exception path for specific rule failures When a template requests an elevated scope or exceeds a threshold eligible for approval Then the system requires a justification (minimum 20 characters) and at least one designated approver selection And sends approval requests to approvers via Slack and email with a deep link to review And blocks publish/apply until all required approvals are received When all required approvers approve within the policy-defined SLA (e.g., 72 hours) Then the template status is set to Approved and publishing is enabled When any approver rejects or the SLA expires Then the request is marked Rejected or Expired, publishing remains blocked, and the requester is notified with reasons
Publication and Application Blocking for Noncompliant Templates
Given a template is noncompliant or pending approval When an admin attempts to publish the template or apply it to users Then the system denies the action (UI controls disabled and API responds 403) and shows a policy evaluation summary And no changes are applied to any user accounts or calendars And an audit log entry captures the denied attempt with actor, reason, and policy evaluation snapshot
Audit Evidence Capture and Export
Given any policy evaluation or approval workflow event occurs (submit, approve, reject, expire, publish) Then the system records immutable evidence including requester, approvers, decisions, timestamps (UTC), policy version, justification text, notification delivery receipts, and the template diff And evidence is searchable by date range, user, template, and rule ID And evidence can be exported by authorized auditors as signed JSON and CSV And read-only auditor roles can access the evidence without the ability to modify it
UI Transparency of Policy Evaluation Results
Given an admin views a template draft or publish modal When the policy is evaluated Then the UI displays pass/fail per rule with rule name, severity, and links to policy documentation And shows the specific fields/scopes that triggered failures And presents next steps to remediate or request approval And re-evaluates the policy on any relevant field change and updates the results in real time

Timebox Tokens

Issue time-boxed OAuth tokens with hard stops and no idle refresh. Tokens expire at the end of a defined window or after inactivity; users get policy-tied renewal prompts. Minimizes blast radius from forgotten connections.

Requirements

Policy-Driven Token Window Configuration
"As a security admin, I want to define time-boxed token policies by role and timezone so that access aligns with working hours and compliance requirements."
Description

Provide an admin-configurable policy engine to define token validity windows by organization, team, role, and integration. Policies support fixed calendar windows (e.g., 9:00–17:00 in user’s local timezone), relative work hours, and explicit inactivity timeouts (e.g., 20 minutes). Include defaults, per-environment overrides, and guardrails (min/max durations). Policies are versioned, auditable, and evaluated at token issuance to embed window and idle constraints. The system resolves user timezone from profile or meeting locale with deterministic fallbacks and handles daylight saving changes without extending access beyond the defined window.

Acceptance Criteria
Scoped Policy Resolution Across Org, Team, Role, and Integration
Given multiple active policies exist at org, team, role, and integration scopes for a user and integration When a token is issued for that user and integration Then the policy applied is the most specific match using precedence: Integration+Role+Team > Integration+Role > Integration+Team > Integration > Role > Team > Org And a deterministic tie-breaker of latest effective_start within the same precedence tier is applied And the evaluation context (org_id, team_id, role_id, integration_id, policy_id, policy_version) is embedded with the token And the embedded window and inactivity settings exactly equal the chosen policy’s values
Fixed Calendar Window in Local Time with DST-Safe Cutoff
Given a policy with a fixed window 09:00–17:00 in the resolved user timezone When a token is issued at any time within that window Then the token hard-expiry is set to the same-day 17:00 wall-clock time in that timezone And on a fall-back DST day the token still expires at 17:00 wall-clock without extending an extra hour And on a spring-forward DST day the token expires at 17:00 wall-clock even if the day has 23 hours And if issuance is attempted after 17:00 local time, issuance is rejected with error_code=window_inactive
Relative Work-Hours Window Evaluation
Given a policy defined as relative work hours (start=workday_start, duration=8h) using the user’s profile schedule When a token is issued during the user’s workday Then the hard-expiry is set to workday_start + 8h for that day, capped by any policy max duration And if issuance is attempted outside the user’s workday, issuance is rejected with error_code=window_inactive And changes to the user’s schedule after issuance do not extend or alter the embedded expiry
Explicit Inactivity Timeout Enforcement with No Silent Refresh
Given a policy with inactivity_timeout=20m When a token has no authenticated requests for 20 consecutive minutes Then the token becomes invalid and subsequent requests return 401 with error_code=idle_timeout And successful authenticated requests before expiry reset the idle timer And refresh attempts cannot extend the hard-expiry beyond the embedded window and are rejected if idle-timeout has elapsed (error_code=idle_timeout) And background or silent renewal flows do not refresh or prolong the token
Defaults, Environment Overrides, and Guardrails
Given an organization default policy P_default and an environment override P_env for environment=staging When issuing a token in staging without a more specific scoped policy Then P_env is applied; when issuing in production, P_default is applied And when an admin saves a policy with duration or inactivity values outside configured guardrails (min/max), validation fails with 400 and field-level errors indicating the violated limits And when values are within guardrails, the policy saves and takes effect at effective_start without retroactively altering existing tokens And tokens already issued remain bound to their embedded constraints despite later policy changes
Policy Versioning and Auditable Issuance
Given an existing policy version v1 and a new version v2 with a later effective_start When issuing tokens before v2.effective_start Then tokens embed v1.version_id; tokens issued after v2.effective_start embed v2.version_id And an audit log entry is created on issuance containing token_id, policy_version_id, evaluation context, resolved timezone, window, idle_timeout, and actor And audit events are immutable and queryable by policy_version_id and token_id And revocation or expiry appends an audit event with a standardized reason code
Deterministic Timezone Resolution with Fallbacks
Given tz_profile=America/New_York, tz_meeting=Europe/Berlin, and org default tz_org=UTC When issuing a token Then the resolved timezone is tz_profile; if tz_profile is missing, use tz_meeting; if both are missing, use tz_org And the chosen timezone is recorded with the token and in audit logs And given identical inputs, timezone resolution yields the same output across repeated requests
Hard-Stop Expiry Enforcement
"As a backend engineer, I want tokens to hard-stop at policy end times with no sliding refresh so that expired credentials cannot be used beyond the allowed window."
Description

Enforce absolute, non-refreshable token expiry at the end of the configured window. Access tokens include immutable exp and nbf claims and are rejected server-side after the window ends regardless of activity. Refresh tokens are disabled for these sessions; silent renewal and sliding expiration are not permitted. Middleware validates window adherence, applies small clock-skew tolerance, and returns structured 401 responses with reason codes for client handling. All services consuming tokens use a shared validator to ensure consistent enforcement.

Acceptance Criteria
Reject Access After Hard-Stop Expiration With Structured 401
Given a timeboxed access token with exp=2025-09-03T10:00:00Z, nbf<=now, and skewTolerance=60s When a protected endpoint is called at 2025-09-03T10:01:01Z (>= exp + skewTolerance) Then the response is HTTP 401 with body {"code":"HARD_STOP_EXPIRED","status":401,"ts":"<ISO-8601>","correlationId":"<uuid>"}, no protected data is returned, and no Set-Cookie or refresh hints are present
Honor Not-Before (nbf) Claim With Clock-Skew Tolerance
Rule: If now < nbf - skewTolerance, the API returns 401 with code "TOKEN_NOT_YET_VALID". Rule: If nbf - skewTolerance <= now < exp + skewTolerance, the token is considered time-window valid and requests are authorized.
No Sliding Expiration Or Silent Renewal During Active Use
Given a token with exp=2025-09-03T10:00:00Z and continuous requests prior to expiration When inspecting the token and server responses across requests Then the exp claim remains unchanged, no new access or refresh token is issued, and after 2025-09-03T10:01:01Z the next call returns 401 with code "HARD_STOP_EXPIRED" And any silent renewal attempts (e.g., prompt=none/iframe) receive 401 with code "REFRESH_DISABLED" and no token is minted
Refresh Tokens Disabled For Timebox Sessions
Given an OAuth session marked as timebox When the token endpoint receives grant_type=refresh_token Then it responds 400 with error="invalid_grant" and JSON field code="REFRESH_DISABLED" and no refresh_token is returned And no Set-Cookie refresh or sliding-session artifacts are ever issued for timebox sessions
Consistent Enforcement Across All Services Using Shared Validator
Given the same expired token is used against Service A, Service B, and Service C When each service processes the request Then each responds with HTTP 401 and identical error schema {code,status,ts,correlationId} with code="HARD_STOP_EXPIRED", and no service returns 2xx And integration tests verify at least three representative services produce identical JSON keys and types in the 401 body
Tokens Include Immutable exp and nbf Claims
Rule: All access tokens issued for timebox sessions contain exp and nbf claims matching the configured window (UTC seconds precision). Rule: Tokens missing exp or nbf are rejected with HTTP 401 and code="MISSING_CLAIMS". Rule: Tokens where exp < nbf are rejected with HTTP 401 and code="INVALID_WINDOW". Rule: Attempts to override exp/nbf via query, body, or headers are ignored; the server validates strictly against token claims. Rule: Tokens failing signature verification are rejected with HTTP 401 and code="INVALID_SIGNATURE".
Inactivity Timeout Tracking
"As a platform engineer, I want tokens to expire after a period of inactivity so that unattended sessions cannot be abused."
Description

Implement server-side idle timers that expire tokens after a configured period of inactivity. Each validated API call updates last-activity time in a low-latency store; if idle > policy threshold, the token is revoked and rejected on next use. Include debounce to avoid excessive writes, cross-node synchronization, and resilience during brief outages without silently extending access. Idle expiry reason is embedded in 401 responses for accurate client prompts and auditing.

Acceptance Criteria
Idle Timeout Enforcement on Next Use
Given policy_idle_threshold_seconds = N and token last_activity = T and the token has had no activity since T When a client presents the token at time t >= T + N Then the request is rejected with HTTP 401 And the response body includes error_code = "idle_timeout" And the token is marked revoked with cause = "idle_timeout" And last_activity is not updated And any subsequent use of the same token also returns HTTP 401 with error_code = "idle_timeout"
Debounced Last-Activity Updates
Given debounce_window_seconds = D And a token sends multiple validated API requests with inter-arrival times < D When processing those requests Then the low-latency store write for last_activity occurs at most once per D per token And in-memory last_activity used for expiry is updated to the timestamp of each validated request And no token expires during active traffic solely due to the debounce mechanism
Cross-Node Consistency for Activity and Revocation
Given a cluster with multiple API nodes and a shared low-latency store And successive requests for the same token are routed to different nodes When last_activity is updated on one node Then other nodes observe the new last_activity within 1 second And if any node revokes the token for idle_timeout, no other node accepts the token thereafter And revocation propagates to all nodes within 1 second
Resilience to Brief Store Outages Without Access Extension
Given the low-latency store becomes unavailable for up to 60 seconds And a token's last known last_activity = T is cached locally with cache_ttl_seconds <= 60 When validating requests during the outage Then the system validates using the cached last_activity without extending token validity beyond T + N And no writes are attempted to refresh last_activity while the store is down And if the token would have expired by idle at any time during the outage, the next request returns HTTP 401 with error_code = "idle_timeout" And upon store recovery, last_activity writes resume and any pending revocations are committed
401 Payload and Auditing for Idle Expiry
Given a token is rejected due to idle timeout When the server returns HTTP 401 Then the response JSON includes fields: error = "unauthorized", error_code = "idle_timeout", policy_idle_threshold_seconds = N, last_activity = ISO8601 timestamp, request_id And server logs an immutable audit event with fields: token_id_hash, reason = "idle_timeout", last_activity, threshold_seconds = N, timestamp, node_id And no sensitive token material or PII is included in the response or logs
Boundary Conditions at Threshold
Given policy_idle_threshold_seconds = N and token last_activity = T When a request arrives at time t Then the request is accepted if t < T + N and rejected if t >= T + N And this behavior is consistent across all nodes And tests include cases where t is within ±1 second of T + N
No Activity Update on Unauthorized or Failed Requests
Given a request using a token that fails authentication or authorization (invalid signature, expired, revoked, or idle_timeout) When the server returns a non-2xx status Then last_activity is not updated in memory or in the low-latency store And only successfully validated requests (2xx after auth) update last_activity
Policy-Tied Renewal Prompt
"As an end user, I want a clear, one-click renewal prompt tied to policy when my session expires so that I can quickly regain access without confusion."
Description

Deliver a client experience that detects 401 responses with policy reason codes and presents a clear, one-click re-auth prompt. The prompt explains why access ended (window end or inactivity), shows the next eligible window if applicable, and deep-links to the correct OAuth flow with PKCE. Support desktop and mobile, SSO-aware flows, and localization. Respect accessibility guidelines and minimize context loss by resuming the original action post-reauth when allowed by policy.

Acceptance Criteria
401 Detection and Policy Reason Prompt
Given the client receives an HTTP 401 containing a supported policy reason code (window_end or inactivity) and a correlation ID When the response handler processes the error Then a re-authentication prompt is displayed within 500 ms And the prompt includes a clear title, a human-readable message derived from the reason code, and a primary "Re-authenticate" action And the correlation ID is available in a details view with only the last 4 characters visible And no prompt is shown for 401 responses without a supported policy reason code
Next Eligible Window Display
Given the reason code is window_end and a next eligible window is defined by policy When the prompt is displayed Then the next eligible window start and end times are shown in the user's local timezone and formatted per the user's locale And daylight saving and policy timezone rules are correctly applied And if no next window is defined, the window section is omitted and a message indicates re-auth is not allowed until a future window
Policy-Tied Renewal Behavior
Given the reason code is inactivity and policy allows immediate renewal When the user selects "Re-authenticate" Then the OAuth flow is initiated immediately Given the reason code is window_end and policy forbids renewal until the next window When the user views the prompt Then the primary action is labeled to indicate availability at the next window and is disabled And no OAuth flow is initiated until the window opens
OAuth Deep Link with PKCE and SSO Awareness
Given the user selects the primary action and policy allows immediate renewal When the OAuth flow is initiated Then the client uses OAuth 2.0 Authorization Code with PKCE (S256) with a verifier length between 43 and 128 characters and at least 128 bits of entropy And includes state (and nonce if OIDC) for CSRF/replay protection And uses a registered redirect URI for the current platform And routes to the tenant IdP authorization endpoint when SSO is enabled; otherwise to the product authorization endpoint And opens in the system browser on mobile and desktop, with same-tab fallback if pop-ups are blocked And upon success, tokens are stored securely and the prompt is dismissed
Action Resumption Post-Reauthentication
Given an original user action failed due to 401 with a supported policy reason code And the action is idempotent and policy allows resumption after re-authentication When re-authentication succeeds and valid tokens are present Then the original action is automatically retried once within 2 seconds And if the retry succeeds, the user sees the expected result with no duplicate side effects And if the retry fails, the user is shown an actionable error referencing the new correlation ID And if policy forbids resumption, the user is returned to the prior context with an explanation that resumption is not allowed
Accessibility: WCAG 2.2 AA Compliance
Given the re-authentication prompt is displayed Then it meets WCAG 2.2 AA: focus moves to the prompt, focus is trapped within until dismissed, Escape/Back dismisses where appropriate, all controls are keyboard accessible, minimum color contrast is >= 4.5:1, and visible focus indicators are present And all interactive elements have accessible names, roles, and states announced correctly by screen readers (NVDA, JAWS, VoiceOver, TalkBack) And the prompt supports 200% zoom and prefers-reduced-motion without loss of content or function And the prompt does not auto-dismiss in under 30 seconds without user input
Localization and Cross-Platform Support
Given the user's locale and language are known When the prompt is displayed Then all visible strings (title, reason, actions, window text) are localized for en, es, fr, de, pt-BR, and ja with English fallback within 200 ms if a translation is missing And date/time values reflect the user's locale formatting and local timezone while honoring policy timezone rules for computations And right-to-left locales render with correct layout mirroring and bidirectional text handling And on mobile, touch targets are >= 44x44 px and the layout adapts from 320 px width and up And the flow functions on latest two versions of Chrome, Firefox, Safari, and Edge, and on iOS and Android default browsers without being blocked by pop-up settings
Least-Privilege Scoped Tokens
"As a security architect, I want tokens to be narrowly scoped and ephemeral so that any compromise has minimal blast radius."
Description

Issue tokens with minimal scopes tailored to the initiating workflow (e.g., calendar read-only vs. write) and integration. Enforce per-action scope checks and deny escalation within the same token. Limit token reuse across services, cap concurrency, and isolate tokens per integration account to minimize blast radius. Provide scope catalogs per provider and automate scope selection from the calling context in TimeTether’s scheduling flows.

Acceptance Criteria
Workflow-Tailored Minimal Scope Issuance
Given TimeTether scheduling flow "Availability Discovery" for provider=google and integration account A When requesting OAuth consent Then the requested scopes are limited to the provider's read-only calendar scopes for account A and no write/admin scopes are requested Given TimeTether scheduling flow "Invite Dispatch" for provider=google and integration account A When requesting OAuth consent Then only the minimal write scopes required to create/send calendar events are requested and no admin scopes are requested Given both flows are needed for the same user When the user completes consent for "Availability Discovery" Then a separate token is issued for "Invite Dispatch" with write scopes rather than expanding the read-only token
Per-Action Scope Enforcement at Runtime
Given a token with read-only calendar scope for provider=google bound to integration account A When TimeTether attempts to create a calendar event via the provider API using this token Then the request is blocked with HTTP 403 and error code "insufficient_scope" and the action is not performed Given the same token When TimeTether reads free/busy data Then the request succeeds with HTTP 200 Given the 403 block occurs Then an audit log entry is recorded with token_id, provider, account_id, requested_action, required_scope, and decision=denied
No Scope Escalation Within a Token
Given a token issued with scopes S1 (read-only) and no write scopes When a refresh or token exchange is attempted requesting additional scopes S2 (write) Then the operation fails with HTTP 400 and error code "scope_escalation_disallowed" and the original token retains scopes S1 only Given scope S2 is required for a workflow When requesting additional permissions Then a new token is issued with scopes S2 and is distinct from the S1 token (different token_id) and S1 continues to function for read-only actions
Token Isolation Per Integration Account
Given a token bound to provider=google and integration account A When used to access resources of integration account B Then the request is blocked with HTTP 403 and error code "account_mismatch" Given tokens exist for accounts A and B When token A is revoked Then token B remains valid and unaffected Given token metadata is inspected via the admin token introspection endpoint Then it includes provider_id, integration_account_id, scopes, issuance_workflow, and concurrency_cap
Service/Provider Binding and Cross-Service Reuse Prevention
Given a token issued for provider=google-calendar When used against microsoft-graph calendar API Then the request is blocked with HTTP 403 and error code "provider_mismatch" Given a token issued for provider=slack When used for any calendar provider Then the request is blocked with HTTP 403 and error code "provider_mismatch" Given an audit stream When a provider mismatch occurs Then a security event is recorded with severity=warning and includes token_id, attempted_provider, bound_provider, and timestamp
Per-Token Concurrency Cap Enforcement
Given the per-token concurrency cap is configured to 3 for provider=google When 4 simultaneous requests are made with the same token Then at least 1 request is rejected with HTTP 429 and error code "concurrency_cap_exceeded" and no more than 3 requests are processed concurrently Given the rejected request is retried after an in-flight request completes Then the retry succeeds with HTTP 200 Given a monitoring endpoint for concurrency metrics When queried Then it shows current in-flight counts per token and provider
Provider Scope Catalogs and Automated Selection
Given the scopes catalog endpoint is queried for provider=google Then the response includes canonical scope ids and metadata for calendar.readonly and calendar.events.write Given the scopes catalog endpoint is queried for provider=microsoft Then the response includes canonical scope ids and metadata for Calendars.Read and Calendars.ReadWrite Given TimeTether scheduling flow "Availability Discovery" for provider=google When automated scope selection runs Then it selects calendar.readonly only Given scheduling flow "Invite Dispatch" for provider=microsoft When automated scope selection runs Then it selects Calendars.ReadWrite only
Admin Revocation & Audit Logging
"As a compliance officer, I want full visibility and control to revoke tokens and view audit trails so that we meet audit and incident response needs."
Description

Provide admin UI and API to search, revoke, and bulk-expire tokens by user, team, integration, or policy version. Log issuance, validations, renewals, idle expiries, window expiries, and revocations with timestamps, reason codes, IPs, and client IDs. Offer export to CSV and webhook/SIEM streaming for compliance. Include anomaly alerts (e.g., repeated expiry-bypass attempts) with configurable thresholds.

Acceptance Criteria
Token Search by User, Team, Integration, and Policy Version
Given an authenticated admin with token-management permission and a dataset of ≥100k tokens across users, teams, integrations, and policy versions When the admin queries tokens using any single filter (userId, teamId, integrationId, policyVersion) or any combination of these plus time range (issuedAt/expiresAt) Then the system returns only matching tokens with fields: tokenId (masked to last 6 chars), userId, teamId, integrationId, policyVersion, issuedAt, lastValidatedAt, expiresAt, status (active/expired/revoked) and supports sort by issuedAt/expiresAt And the response includes pagination (limit up to 100, cursor/nextToken) and totalCount And median response time ≤ 400 ms and p95 ≤ 800 ms for indexed queries on 100k tokens And nonexistent filter values return an empty result with 200 status And malformed queries return 400 with a validation error payload
Single and Bulk Token Revocation with Immediate Propagation
Given an active token exists and an admin initiates revocation via UI or API When the admin revokes a single token providing a required reasonCode and optional note Then the token becomes invalid across all validation endpoints within 5 seconds and subsequent validations fail with error invalid_token and subcode TOKEN_REVOKED And an audit record is written with eventType=revocation, tokenId (masked), adminId, reasonCode, ip, clientId, policyVersion, timestamp (UTC ISO-8601 ms) Given a set of tokens matching a filter (by userId, teamId, integrationId, policyVersion, or status=active) When the admin performs a bulk revocation with an explicit confirmation step showing the impacted count Then all currently active matching tokens are revoked atomically in batches with progress feedback; success and failure counts are displayed; partial failures are retried up to 3 times and reported per token And bulk revocation requires recent MFA (≤ 5 minutes) and records a single parent audit entry with a child entry per token And revocation requests are idempotent by requestId; repeating the same requestId produces no duplicate effects
Comprehensive Audit Logging of Token Lifecycle Events
Given token lifecycle events occur (issuance, validation, renewal, idleExpiry, windowExpiry, revocation) When each event is processed Then an immutable audit log entry is appended within 2 seconds containing: eventType, tokenId (masked), userId, teamId, integrationId, clientId, policyVersion, actorType (system/admin), ip, timestamp (UTC ISO-8601 ms), reasonCode (where applicable) And no full token secrets are stored; token identifiers are irreversibly truncated or hashed with salt And audit entries are queryable by all fields, time-ranged, and paginated; writes are append-only; updates/deletes are rejected with 405 And clock skew ≤ 2 seconds is tolerated without misordering within the same token’s timeline And audit retention is configurable (default 400 days, min 90, max 2555) and enforced And p95 write latency ≤ 500 ms and sustained write throughput ≥ 500 events/sec without loss
CSV Export of Filtered Audit Logs
Given an admin selects a time range, filters, and columns for audit events When the admin requests a CSV export Then the system generates a UTF-8 CSV conforming to RFC 4180 with a header row and only the selected columns, with timestamps in UTC ISO-8601 And exports include all records matching filters up to 1,000,000 rows per file; larger result sets are split into sequential files with deterministic naming And the export completes within 5 minutes for 1,000,000 rows and provides a signed download URL valid for 15 minutes And the export job status is trackable (queued/running/completed/failed) and emits an audit entry with requester, parameters, row count, and file ids And canceled exports terminate processing and produce no partial files
Webhook/SIEM Streaming of Audit Events with Delivery Guarantees
Given an admin configures an outbound destination with URL, auth (Bearer or Basic), and shared secret for signatures When new audit events are generated Then the system delivers events as JSON via POST in batches of up to 500 events or 1 MB, or every 10 seconds, whichever comes first And each request includes an HMAC-SHA256 signature header, an idempotencyKey, and sequence numbers for ordering And 2xx responses mark the batch delivered; 5xx triggers exponential backoff retries for up to 24 hours; 429 respects Retry-After; persistent 4xx disables the destination and raises an alert And delivery is at-least-once with consumer-side deduplication via idempotencyKey; p95 end-to-end latency ≤ 15 seconds under normal load And the configuration has a test button that sends a signed health event and shows lastAttemptAt, lastSuccessAt, failureRate, and queueDepth
Anomaly Alerts for Expiry-Bypass Attempts with Configurable Thresholds
Given a detection rule for expiry-bypass attempts (e.g., renewal or validation after idle or window expiry) When the count of such attempts for any user, team, clientId, or IP exceeds a configurable threshold T within window W Then the system emits a deduplicated alert via configured channels (email, Slack webhook, PagerDuty/webhook) including rule id, entity keys, counts, timeframe, sample event ids, and recent IPs And defaults are T=5 and W=10 minutes, per-workspace configurable; minimum values are validated (T≥1, W≥1m) And alerts are rate-limited to at most one per rule per entity every 30 minutes and are themselves audit-logged And a test mode allows safe generation of a synthetic alert without affecting quotas
Access Control and Authorization for Admin UI/API
Given various user roles exist (Viewer, Analyst, TokenAdmin, Owner) When accessing token search, revocation, audit logs, export, or streaming configuration via UI or API Then only users with TokenAdmin or higher can perform revocations and configure streaming; Analysts can search and export; Viewers can view logs only; others receive 403 with an authorization error code And all admin endpoints require OAuth scopes: timetether.tokens.read for read-only actions and timetether.tokens.manage for write actions And destructive actions (bulk revocation, destination disable) require recent MFA and are blocked if MFA is older than 12 hours And every access attempt (allowed or denied) is audit-logged with subject, action, scope, and ip
Multi-Provider OAuth Compatibility
"As an integration developer, I want Timebox Tokens to work across major OAuth providers so that we can connect calendars and tools consistently."
Description

Support major OAuth providers used by TimeTether (Google, Microsoft, Slack, Zoom) with provider-specific claim mapping, PKCE, and both JWT and opaque token handling. Normalize scopes and expiry semantics across providers while honoring each provider’s constraints. Provide configuration for tenant restrictions, consent screens, and sandbox/production environments. Implement robust error handling and retries for provider outages without relaxing timebox guarantees.

Acceptance Criteria
Google OAuth with PKCE, Scope Normalization, and Timebox Enforcement
Given a timebox policy with absolute_window_end = now + 60m and inactivity_timeout = 15m And Google OAuth is configured with PKCE and access_type=online When a user completes Google OAuth authorization Then the authorization request includes code_challenge_method = S256 and a valid code_challenge And the token exchange uses the matching code_verifier And provider_user_id is mapped from id_token.sub and email from id_token.email where email_verified = true And access_token (opaque) and id_token (JWT) are stored with absolute_expiry = min(provider_expires_at, absolute_window_end) And inactivity_expiry is set to now + 15m and extends only on successful user-initiated API calls within the window And no refresh_token is requested or stored And any call after absolute_window_end or 15m inactivity returns 401 timebox_expired and triggers a policy-tied re-auth prompt And no background task refreshes or extends the token
Microsoft Entra (Azure AD) Tenant Restriction and Consent Configuration
Given Azure AD OAuth is configured with allowed_tenant_ids = [T1, T2] And required scopes are set and a custom consent display name is configured When a user from tenant T3 (not allowed) attempts sign-in Then the flow is rejected post-token validation with error tenant_not_allowed, and no tokens are persisted When a user from tenant T1 attempts sign-in Then PKCE is enforced and id_token.tid maps to tenant_id and oid (or sub) maps to provider_user_id And the consent screen displays the configured application name and only the declared scopes And issued tokens are constrained to absolute_window_end and inactivity_timeout as per policy And no incremental scopes beyond the normalized set are accepted; requests with extra scopes are denied with 400 invalid_scope
Slack and Zoom Claim Mapping with Scope Normalization
Given Slack OAuth completes with scopes = ["users:read", "channels:read"], team.id = S_TENANT, authed_user.id = S_USER When tokens are issued Then claims are mapped to normalized identity: tenant_id = S_TENANT, provider_user_id = S_USER, provider = "slack" And normalized_scopes = ["identity.read", "channels.read"] per mapping table; unmapped scopes cause the flow to fail with 400 invalid_scope_mapping Given Zoom OAuth completes with scopes = ["user:read:admin"], id_token.accountId = Z_TENANT, id_token.sub = Z_USER Then claims are mapped to normalized identity: tenant_id = Z_TENANT, provider_user_id = Z_USER, provider = "zoom" And normalized_scopes = ["identity.read"] per mapping table And for both providers, token absolute_expiry = min(provider_expires_at, policy.absolute_window_end) and inactivity timeout is enforced at policy.inactivity_timeout
JWT and Opaque Token Validation and Caching
Given a provider returns a JWT (e.g., Google id_token or Zoom id_token) When validating the token Then JWKS is fetched and cached (TTL 24h, preemptive refresh on kid miss), and iss, aud, exp, iat, nbf are verified with clock_skew <= 60s And tokens with invalid signature or claims are rejected and not persisted Given a provider returns an opaque access_token (e.g., Google access_token, Slack access_token) When first using the token Then the system validates via the provider’s validation endpoint (e.g., Slack auth.test, Zoom userinfo) or introspection if available, with a 2s timeout and 2 retries with jitter And the validated TTL is recorded and capped by the timebox absolute_window_end and inactivity timeout And on any validation failure or revocation signal, the connection is marked revoked, retries stop, user is prompted to re-auth, and timebox constraints are not relaxed
Provider Outage Resilience without Extending Timebox
Given a provider returns 5xx, 429, or times out during API calls within an active timebox When a call fails Then the system retries with exponential backoff and jitter starting at 500ms up to 30s, for a total retry window of min(10m, time remaining before absolute_window_end) And retries cease immediately upon reaching absolute_window_end; no token lifetime is extended and no refresh is attempted beyond the window And a user-visible, provider-specific error is surfaced after retries exhaust, with guidance to retry or reconnect And all failures and retries are logged with provider, request type, correlation_id, and outcome; SLO metrics are emitted
Inactivity Timeout Enforcement Across Providers
Given policy.inactivity_timeout = 15m and a token is active across any provider When no successful user-initiated calls occur for 15m Then the token is invalidated locally and any subsequent call returns 401 timebox_inactivity_expired And background polling or health checks do not reset the inactivity timer; only user-initiated actions within the absolute window do And last_activity_at and expiration_reason = "inactivity" are recorded in audit logs And rejection behavior is consistent across Google, Microsoft, Slack, and Zoom integrations
Sandbox vs Production OAuth Configuration Isolation
Given sandbox and production environments are configured with distinct client_ids, client_secrets, redirect_uris, consent text, and allowed tenants When environment = sandbox Then only sandbox credentials and redirect_uris are used; production tokens are rejected with 403 wrong_environment When environment = production Then only production credentials and redirect_uris are used; sandbox tokens are rejected with 403 wrong_environment And switching the environment flag updates active configuration within 5 minutes without requiring a deploy And a health check verifies provider endpoints per environment; misconfigurations block new sign-ins and raise an alert; no data or tokens cross environments

Policy Simulator

Preview what a scheduler can do before granting access. Simulate series creation across teams to see allowed actions, blocked steps, and recommended policy tweaks. Cuts trial-and-error and accelerates safe rollout.

Requirements

Sandbox Policy Ingestion & Validation
"As an org admin, I want to import and validate scheduling policies into a sandbox so that I can run accurate simulations without granting write access."
Description

Provide a secure, read-only pathway to import existing TimeTether policies and related metadata (roles, teams, work windows, holidays, fairness rotation settings, compliance constraints) into a sandbox context. Support direct pull from an authorized org with metadata-only scopes and file-based import (JSON/YAML). Perform schema/version validation, reference resolution (users, groups, calendars), and compatibility checks, surfacing gaps like missing scopes or deprecated rules. Produce an immutable, normalized snapshot that underpins reproducible simulations and prevents false positives/negatives by ensuring policy accuracy without requiring write permissions.

Acceptance Criteria
Direct Org Pull with Metadata-Only Scopes
Given an authorized org token with only metadata read scopes and no write scopes When the sandbox import is requested for policies and related metadata Then the system performs a read-only pull and returns 200 with an import report including counts by entity type And Then zero write operations are executed against the source org (0 write API calls logged) And Then an audit log entry is recorded with actor, scopes, source org ID, and timestamp And Then if the token includes any non-metadata scopes, the import is rejected with 403 and error code SANDBOX_SCOPE_VIOLATION listing offending scopes
File-Based Policy Import (JSON/YAML)
Given an upload with content-type application/json or application/yaml (or text/yaml) containing a policy bundle When the file is uploaded to the sandbox importer Then the system parses it without external network calls and returns 200 with parsed entity counts by type And Then if the file is syntactically invalid, the importer responds 422 with error code SANDBOX_PARSE_ERROR including 1-based line and column And Then files with unsupported MIME types or extensions are rejected with 415 UNSUPPORTED_MEDIA_TYPE
Schema and Version Validation
Given a policy bundle with a declared schemaVersion When schema and version validation runs Then if schemaVersion is supported, validation passes with 0 critical errors and any non-critical issues are returned as warnings with codes And Then if schemaVersion is unsupported, respond 409 with error SANDBOX_UNSUPPORTED_VERSION and include supportedVersions array And Then deprecated fields or rules emit warnings with codes (e.g., DEPRECATED_RULE_X) and locations, without blocking import unless marked breaking And Then unknown fields emit warnings tagged UNKNOWN_FIELD with JSONPath/YAMLPath locations
Reference Resolution for Users, Groups, and Calendars
Given policies reference users, groups, and calendars by ID or external key When reference resolution executes against the authorized org directory and calendar metadata Then each reference is marked RESOLVED or UNRESOLVED with reason (MISSING, AMBIGUOUS, NO_SCOPE) And Then the import report lists counts resolved/unresolved per type and up to 100 sample unresolved identifiers And Then if any UNRESOLVED references exist, import completes with status PARTIAL and simulator steps depending on them are pre-marked BLOCKED
Compatibility Checks and Gap Surfacing
Given resolved policies with fairness rotation, work windows, holidays, and compliance constraints When compatibility checks run Then conflicts (e.g., rotation requires attendees outside work windows) are flagged with severity BLOCKER or WARNING, each with machine-readable codes and remediation text And Then missing scopes that prevent enforcement are listed with requiredScopes[] and impact description And Then recommended policy adjustments are produced as a non-applied diff preview attached to the report
Immutable Normalized Snapshot Creation
Given an import completes without critical validation failures When the normalized snapshot is generated Then the system stores an immutable snapshot with snapshotId, contentHash (SHA-256), createdAt, and schemaVersion And Then repeated imports of identical input yield the same contentHash and byte-for-byte equal normalized JSON And Then any attempt to modify or delete a snapshot returns 405 METHOD_NOT_ALLOWED and the snapshot remains unchanged And Then the simulator can access the snapshot by snapshotId only, with no live org calls during simulation
Deterministic, Reproducible Simulation Inputs
Given a snapshot is selected as the simulator input When the same simulation is executed twice with identical parameters and fixed clock time Then results are identical and the execution logs cite the same snapshotId and contentHash And Then simulations perform zero live org API calls; all data is read from the snapshot
Series Creation Simulation Engine
"As a scheduling lead, I want to simulate series creation across teams so that I can see which actions are allowed or blocked before rollout."
Description

Implement a dry-run scheduler that evaluates recurring meeting series across selected teams, time ranges, and recurrence rules without performing calendar writes. Apply TimeTether constraints including timezone analysis, work windows, holiday/PTO calendars, fairness-driven rotation, meeting load caps, and after-hours limits. Produce proposed series instances labeled allowed/blocked with machine-readable reasons. Ensure determinism (seeded runs), performance at scale (10–100 teams, 10k+ instances), and isolation from production data. Support strategy toggles (e.g., fairness weight variants) and metadata-only availability models to respect least-privilege access.

Acceptance Criteria
Deterministic Seeded Simulation Output
Given identical inputs and a specified seed value When the simulation is executed multiple times Then the ordered list of proposed instances, allowed/blocked statuses, and reason payloads are byte-identical across runs And the response includes the seed used and a stable simulation version identifier Given a different seed with the same inputs When the simulation is executed Then only tie-break decisions subject to randomness change and all constraint outcomes remain consistent
Scale Performance and Resource Boundaries
Given 100 teams and ≥10,000 candidate instances over the requested range When the simulation runs on a 4 vCPU, 16 GB RAM node Then it completes within 30 seconds wall time and peak memory usage remains ≤ 4 GB And throughput is ≥ 400 instances evaluated per second And the response includes metrics: total_instances_evaluated, wall_time_ms, peak_memory_mb
No Side Effects and Least-Privilege Operation
Given the engine is executed with metadata-only availability and no calendar write scopes When the simulation completes Then no external calendar writes/updates are performed (verified via audit logs) And only read/availability endpoints are accessed And outputs are persisted to a simulation-only namespace separate from production series data
Constraint Enforcement with Machine-Readable Reasons
Given instances that violate work windows, holidays/PTO, after-hours limits, meeting load caps, or timezone constraints When the simulation evaluates them Then such instances are labeled blocked with at least one canonical reason_code from {WORK_WINDOW, HOLIDAY_PTO, AFTER_HOURS_LIMIT, LOAD_CAP, TIMEZONE_CONFLICT, FAIRNESS_ROTATION, OTHER} And blocked instances include reason_detail and blocking_actor_id(s) And compliant instances are labeled allowed and include evaluated_constraints with pass/fail flags
Fairness Rotation and Strategy Toggles
Given strategy_toggles specifying fairness_weight in {low, default, high} When simulating recurring series across teams with uneven meeting burdens Then the output includes fairness metrics per team/user (e.g., after_hours_burden, rotation_gap, fairness_score) And fairness_score(high) ≥ fairness_score(default) ≥ fairness_score(low) for the same inputs And with fairness_weight=high, the max team after-hours burden delta ≤ 10% of the global average over the simulated horizon
Recurrence Expansion and Timezone Normalization
Given iCal RRULEs (e.g., WEEKLY with BYDAY and INTERVAL) and a source timezone spanning DST transitions When expanded over the requested date range Then generated occurrences match the RRULE count within range minus excluded holiday/PTO dates And each instance includes source_time, normalized_utc_time, and participant_local_times[] And nonexistent/ambiguous local times due to DST are handled per policy (skip or shift) with an explicit reason_code
Meeting Load Caps and After-Hours Limits Compliance
Given per-user and per-team caps (e.g., max 10 hours/week per user; max 4 standing series per team) and per-user after-hours limits (e.g., ≤ 1 instance per 14 days outside work window) When the simulation evaluates proposed instances Then no allowed instance causes any cap or limit to be exceeded And instances that would exceed a cap are blocked with reason_code LOAD_CAP or AFTER_HOURS_LIMIT and include the exceeded_threshold and actor references And allowed instances include cap_usage_before and cap_usage_after for each affected actor
Rule Rationale & Decision Trace
"As a compliance reviewer, I want a trace of each simulated decision so that I can audit policy enforcement and understand blockers."
Description

Generate a step-by-step decision trace for each simulated action, mapping outcomes to specific policy rules, rule IDs, and input facts (e.g., user timezones, work windows, rotation state). Provide human-readable explanations and structured reasons codes, with filtering, search, and deep links back to policy definitions. Capture both allow and block outcomes and surface conflicting or overlapping rules. Enable export of traces for audit and attach them to simulation reports for review by compliance and security stakeholders.

Acceptance Criteria
Outcome-to-Rule Mapping in Decision Trace
Given a simulated series creation across two teams with a policy set containing at least five rules with distinct IDs and versions When the simulator executes and evaluates availability, work windows, rotation fairness, and invite permissions Then the decision trace lists each evaluation step in execution order And each step includes fields: stepIndex, timestamp, evaluator, ruleName, ruleId, ruleVersion, policyPath, outcome (allow|block|skip), matchedConditions, inputFacts (timezone, workWindow, rotationState, userRole), and finalDecision And for each block outcome, the blocking ruleId and reasonCode are present And the trace includes at least one allow and one block step for the provided fixture And 100% of steps include non-empty ruleId and outcome values
Human-Readable Explanations and Reason Codes
Given a known fixture producing both allow and block outcomes When the decision trace is generated Then each step contains a humanReadableExplanation string of 300 characters or fewer that accurately summarizes why the outcome occurred And each step contains a reasonCodes array with at least one code formatted as NAMESPACE:RULE_ID:REASON_KEY And the pair (humanReadableExplanation, reasonCodes[0]) matches the expected values defined in the fixture for that ruleId and outcome And all explanation strings are plain text, newline-free, and safe for UI rendering (no HTML) And 100% of steps include both humanReadableExplanation and reasonCodes
Trace Filtering, Search, and Deep Links to Policy
Given a decision trace with at least 200 steps containing mixed outcomes and multiple ruleIds When the user applies filters outcome=block and ruleId=R-102 Then only steps matching both filters are displayed and the visible count equals the filtered step count And clearing filters restores the original step count When the user searches for the phrase "work window" Then steps with humanReadableExplanation or matchedConditions containing the phrase are highlighted and counted And clicking the deep link for any step opens the policy definition viewer at the exact policyPath anchor for that rule within 1 second on reference hardware And every rule reference in the trace includes a valid deep link URL
Conflict and Overlap Detection with Precedence Rationale
Given a policy where rule R-200 blocks after-hours meetings and rule R-201 allows after-hours for items labeled "urgent" And a simulated series is created outside a participant's work window with label "urgent" When the simulator evaluates the action Then the decision trace includes a Conflicts section listing R-200 and R-201 with their evaluated outcomes and matched conditions And the trace includes a precedenceResolution entry indicating the precedence policy (e.g., deny-overrides) and the winning ruleId And a warning badge appears in the UI and trace.summary.conflictsCount equals 1 And the trace includes a whatIf.alternativeDecision showing the decision if precedence were inverted
Exportable Audit Trace with Redaction and Schema Validation
Given any generated decision trace of at least 50 steps When the user exports the trace to JSON and PDF Then the JSON validates against schema version TRACE_SCHEMA_v1 and includes metadata: simulationId, policyVersionHash, orgId, actorId, timestamp, simulatorVersion, stepCount And sensitive fields (email, externalId) are redacted when Redact PII is enabled, and unredacted when it is disabled for authorized roles only And the PDF export contains a summary page plus paginated steps with ruleId, outcome, reasonCodes, and deep links rendered as URLs And re-importing the exported JSON reproduces an identical in-app trace view (step order and content match)
Attach Decision Traces to Simulation Reports with Access Controls
Given a completed simulation run titled "Team Europe–APAC Rollout" When the user selects Attach decision trace and chooses two traces Then the generated report includes immutable snapshot copies of both traces and permanent deep links And users with roles ComplianceReviewer or SecurityAnalyst can view the attachments; users without ViewPolicy permission receive HTTP 403 via API and an access denied UI state And the Reports API returns traceAttachmentIds that match the attached traces in the UI And attachments retain for at least 365 days unless explicitly deleted by an OrgAdmin
What-If Scenario Builder & Side-by-Side Comparison
"As a program manager, I want to tweak key policy parameters and compare outcomes side-by-side so that I can converge on a safe configuration faster."
Description

Offer an interactive workspace to adjust key policy parameters (work windows, after-hours thresholds, fairness weights, meeting cadence) and input sets (teams, participants, time ranges) to run comparative simulations. Display side-by-side diffs of allowed/blocked counts, fairness metrics, and after-hours impact, with visualizations (heatmaps, rotation balance). Provide guardrails (change limits, scope selection) and the ability to save, label, and rerun scenarios using the same snapshot for repeatability.

Acceptance Criteria
Adjust Policy Parameters & Inputs in Workspace
Given I am in the What‑If workspace When I select teams (up to 10), participants (up to 200), and a time range (1–12 weeks) Then only teams I have simulator scope for are selectable And participants auto-filter to the selected teams And the time range cannot exceed 12 weeks and cannot start more than 1 week in the past When I modify work windows, after-hours thresholds, fairness weights, and meeting cadence Then each field enforces its defined bounds: work windows within 00:00–23:59 by locale; after-hours threshold 0–12 hours; fairness weights 0.0–1.0; cadence 1–8 weeks And invalid entries are rejected with inline error messages within 300 ms And a change summary shows the count of modified parameters and inputs
Run Two Scenarios and View Side-by-Side Diffs
Given baseline Scenario A is saved and Scenario B is created by modifying ≤5 parameters When I run a comparative simulation Then results render within 20 seconds for up to 8 teams and 150 participants And both scenarios display: allowed count, blocked count, block reasons by rule, fairness index (Gini 0–1), after-hours meetings count and percentage And deltas (B − A) are computed and color-coded (improvements green, regressions red) And totals reconcile: allowed + blocked = total proposed meetings for each scenario
Visualize Heatmap and Rotation Balance
Given a scenario has completed simulation When I open the Visualizations tab Then a timezone-normalized heatmap displays feasible slots by day/hour for the selected time range And a rotation balance chart displays per-participant meeting load over the cadence window And hovering any cell or segment reveals counts and percentages that match tabular metrics with zero discrepancy And toggling between scenarios updates both charts within 500 ms
Enforce Guardrails on Change Limits and Scope
Given a baseline scenario is pinned When I attempt to run a comparison where more than 5 policy parameters differ or unscoped teams are included Then the Run action is disabled and a guardrail message identifies the offending changes And I can proceed only by narrowing scope or reducing differences to ≤5, after which Run becomes enabled And scope selection requires at least 1 team and at most 10 teams
Save, Label, and Rerun Scenario from Snapshot
Given I save a scenario with label, description, and tags Then a snapshot captures selected teams, participants, calendars, policies, and time range with immutable IDs and a checksum And labels must be unique per workspace; attempting a duplicate label prompts a rename flow When I rerun the saved scenario after org changes Then results match the original snapshot exactly (all counts and metrics equal) and are marked "Replayed from Snapshot" And a rerun log records user, timestamp, and snapshot checksum
Accurate Allowed/Blocked Simulation per Policy
Given team policies include constraints (e.g., work windows, after-hours caps, fairness rotation) When I simulate series creation across selected teams Then each proposed step is marked Allowed or Blocked with a specific rule code and explanation And the count of blocked steps by rule equals the sum of rule-level explanations And changing a single policy parameter produces a corresponding change in counts on the next run that can be traced to that rule
Recommendation Engine & Policy Diff
"As a security-conscious admin, I want recommendations and diffs for minimal policy changes so that I can resolve blockers without over-permissioning."
Description

Analyze simulation outcomes to detect systematic blockers and near-miss opportunities, then generate minimal, risk-aware policy tweaks (e.g., expand a work window by 30 minutes for Team A on Wednesdays, adjust fairness weight from 0.6 to 0.7). Produce a structured policy diff with impact estimates (after-hours reduction, fairness delta, risk score) and rationale tied to trace evidence. Allow users to accept/reject individual recommendations and bundle them into a draft change set for review.

Acceptance Criteria
Systematic Blocker Detection Across Simulations
Given a completed simulation dataset containing at least 10 teams and 1,000 meeting attempts When the engine analyzes the outcomes Then it flags any blocker category that occurs in ≥ 15% of attempts within a scope (Org|Team|Role|Timezone|Day-of-Week) or ≥ 20 occurrences (whichever is greater) as a "systematic blocker" And the flag includes scope, blocker taxonomy key, occurrence count, affected attempt IDs (list length ≥ 20), and frequency percentage rounded to 1 decimal And processing completes in ≤ 30 seconds for datasets up to 10,000 attempts on a standard worker
Near-Miss Opportunity Identification and Minimal Tweaks
Given failed scheduling attempts with candidate slots within 45 minutes of a work-window boundary or fairness conflicts within 0.10 of the resolution threshold When the engine evaluates tweakability under guardrails Then it generates a near-miss recommendation proposing the minimal change that converts ≥ 80% of the affected failures to successes in re-simulation And any proposed work-window expansion is ≤ 30 minutes on specified days per team; fairness weight |Δ| ≤ 0.10; max 1 tweak per policy key per scope in a single recommendation And the recommendation includes meetingsUnblocked (integer ≥ 1), afterHoursReductionPct (0–100, 1-decimal), fairnessDelta (-1.0–1.0, 3-decimal), and riskScore (0.0–1.0)
Risk-Aware Recommendation Scoring and Conflict Marking
Given a candidate recommendation produced by the engine When computing risk and validating minimality Then a riskScore in [0.0, 1.0] is produced with a list of contributing risk factors in the rationale And items with riskScore > 0.40 are labeled "High Risk" by default configuration And minimality holds: no alternative tweak with strictly smaller magnitude achieves equal-or-better meetingsUnblocked for the same scope And recommendations targeting the same policyPath with overlapping scopes are mutually marked with a shared conflictGroupId
Structured Policy Diff JSON Output with Impact Estimates
Given one or more generated recommendations When exporting the policy diff Then the output is a JSON array where each item contains: id (UUID), policyPath (string), scope.type (Org|Team|Role), scope.id (string), oldValue, newValue, impact.afterHoursReductionPct (0–100 numeric, 1-decimal), impact.fairnessDelta (-1.0–1.0 numeric, 3-decimal), impact.meetingsUnblocked (integer ≥ 0), riskScore (0.0–1.0 numeric, 2-decimal), rationale.text (string ≥ 20 chars), rationale.traceIds (array length ≥ 1), evidence.sampleSize (integer ≥ 1), confidence (0.0–1.0 numeric) And each rationale.traceId references an existing simulation run ID And schema validation passes and export completes in ≤ 2 seconds for up to 500 items
Accept/Reject Flow with Persistent State and Recalculation
Given a list of recommendations displayed to a user with the Policy Editor role When the user accepts or rejects an item Then the item’s state changes to Accepted or Rejected within 1 second and persists after refresh And Accepted items are added to the current draft change set; Rejected items are excluded And aggregated estimates (total afterHoursReductionPct, total fairnessDelta, total meetingsUnblocked, max riskScore) update within 2 seconds And the user can undo the most recent accept/reject action within the current session
Draft Change Set Creation, Permissions, and Audit Trail
Given at least one Accepted recommendation When the user creates a draft change set Then the draft stores: title, description, author, timestamp, versionId, included recommendation ids, aggregated impacts (afterHoursReductionPct, fairnessDelta, meetingsUnblocked), max riskScore, and an exportable JSON diff And only users with Policy Editor (or higher) can create or submit drafts; Viewers can only export And upon submit, the draft is locked against edits and an audit record is created referencing included recommendation ids and their rationale.traceIds
Shareable Simulation Report & Draft Export
"As a stakeholder, I want a shareable report and the option to export approved tweaks to a draft policy so that I can gather approvals and move to rollout efficiently."
Description

Create a shareable report summarizing scenario inputs, allowed/blocked actions, key metrics (after-hours exposure, fairness balance, load distribution), decision traces, and recommended tweaks. Support role-based access, expiring links, and redaction of sensitive identifiers. Enable one-click export of accepted recommendations to a draft policy artifact (TimeTether draft config or a pull request to a policy repo) with embedded changelog and approvals metadata, without applying changes to production until explicitly promoted.

Acceptance Criteria
Report Generation with Complete Contents
Given a completed policy simulation across multiple teams with defined work windows and policies When the user clicks Generate Report Then a report artifact is created within 5 seconds And the report includes: scenario inputs summary (teams, timezones, work windows), allowed actions, blocked steps with policy rule IDs, key metrics (after-hours exposure % and counts per participant, fairness balance variance, load distribution meetings/person/week), decision traces per action, and recommended policy tweaks with expected metric deltas And the report has a unique version ID and immutable checksum And metric totals are internally consistent (per-participant sums match totals; percentages sum within ±1% rounding tolerance)
Expiring, Shareable Link with Access Controls
Given the report owner requests a shareable link with a TTL and role scope When a viewer with an allowed role opens the link before expiry Then the report loads successfully And when the same link is accessed after expiry or after revocation Then the server returns HTTP 403 with no report content And the link TTL is configurable between 1 hour and 30 days And all accesses are logged with timestamp, user, and IP address
Role-Based Redaction of Sensitive Identifiers
Given a report contains sensitive identifiers (e.g., emails, user IDs, calendar IDs, repo names) When a viewer without PII permission accesses the report or exports it Then all sensitive identifiers are replaced with stable pseudonyms within that report And the redaction is deterministic per report (same input maps to same pseudonym) and irreversible from the client And no raw identifier patterns (emails, UUIDs) appear in HTML responses, API payloads, or exported files And a viewer with PII permission sees full identifiers for the same report
One-Click Export to TimeTether Draft Config
Given the user has marked one or more recommendations as Accepted When the user clicks Export to Draft Config Then a TimeTether draft policy artifact is created containing only the selected changes And the artifact includes an embedded changelog (who, what, when, why, expected metric deltas) and a backlink to the report ID And the artifact is marked Draft, versioned, and does not modify the active production policy And the artifact ID and URL are returned via UI and API And if schema validation or merge conflict occurs, the export fails with a descriptive error and no partial draft is created
One-Click Export as Pull Request to Policy Repo
Given a connected policy Git repository with write permission and a target branch is configured When the user clicks Create Pull Request for the selected recommendations Then a PR is opened containing only the proposed config changes And the PR title and body include a summary, changelog, approvals metadata, and link to the originating report And the PR is labeled policy-simulated and passes schema validation CI And if repository permissions are insufficient or CI fails, the UI displays the failure reason and no merge occurs
Embedded Changelog and Approvals Metadata
Given an export (draft config or PR) is created from a report When inspecting the export metadata Then a machine-readable changelog (JSON) lists rule additions, modifications, and removals with before/after values and expected metric deltas And approvals metadata records required approvers by role, current approvers, timestamps, and decisions And the metadata remains intact when the artifact is re-imported into the simulator
Safety: No Production Changes Until Explicit Promotion
Given one or more exported artifacts exist When observing the production scheduler before any promotion by a user with PolicyAdmin role and MFA Then the production policy checksum remains unchanged And the UI displays a Not applied to production banner on the report and exports And attempts to promote without required role or MFA are blocked with an audit log entry And upon valid promotion, the production checksum updates and an audit record links the promotion to the artifact and report

Access Ledger

Immutable, per-scheduler access logs with who/what/when/why, including scopes, calendars touched, and approving owner. Search, filter, and export to SIEM/CSV for effortless audits and incident forensics.

Requirements

Immutable Ledger and Integrity Verification
"As a security auditor, I want to verify that access logs are tamper-evident so that I can trust the evidence during audits and investigations."
Description

Provide an append-only, tamper-evident access ledger for all scheduler-related operations. Each event is chained via cryptographic hashes, time-stamped in UTC, and signed to prevent undetected modification or deletion. Support a verification API/CLI that recalculates the chain and reports integrity proofs for a given time range or set of schedulers, with periodic external anchoring to strengthen evidentiary value. Ensure high write throughput with backpressure, idempotent event ingestion, and durable storage with write-once semantics and disaster recovery replication.

Acceptance Criteria
Append-Only Tamper Evidence via Hash Chain and Signatures
Given a valid event payload with correct previous_hash, when it is ingested, then the ledger persists it with fields: id, scheduler_id, action, scopes, calendars, actor_id, reason, timestamp_utc (RFC3339 Z), previous_hash, hash, signature. Given any attempt to modify a persisted event, when the verification tool recalculates the chain, then it marks the chain invalid at the modified event with reason=hash_mismatch. Given any attempt to delete an event from the chain, when verification runs, then it fails due to previous_hash mismatch at the first subsequent event. Given a persisted event, when its signature is verified against the active public key at its timestamp, then verification returns valid; if the event is altered, then signature verification returns invalid. Given a request to append an event with incorrect previous_hash, when processed, then the request is rejected with HTTP 409 and no write occurs.
Integrity Verification over Time Range and Scheduler Set
Given a time range and a set of scheduler_ids, when the verify API/CLI is called, then it returns: chain_status (PASS|FAIL), total_events, first_event_id, last_event_id, first_hash, last_hash, failing_event_ids[], and anchor_references covering the range. Given a failing chain, when the --explain flag is used, then the tool returns the first failing event_id and reason in {hash_mismatch, missing_event, signature_invalid, timestamp_invalid}. Given a verification request for up to 10,000,000 events, when executed, then it streams progress (>= every 5s), uses <= 512 MB memory, and completes within 30 minutes at p95.
Idempotent Event Ingestion with Deduplication
Given two requests with the same idempotency_key within 24 hours, when processed concurrently or sequentially, then exactly one ledger event is persisted and both calls return the same event_id with HTTP 200. Given a retried request after an unknown client timeout, when the original write succeeded, then the retry returns the original result without creating a new event. Given duplicate requests with the same idempotency_key crossing a failover, when processed, then no duplicate events are created and the response is consistent across regions.
Backpressure Under High Throughput
Given sustained ingest at 2,000 events/second per shard for 10 minutes, when the system is healthy, then p99 append latency <= 500 ms and error rate <= 0.1%. Given incoming rate exceeds the configured threshold by >= 50%, when queues reach the high-watermark, then the server responds with HTTP 429 and a Retry-After header and does not drop acknowledged events. Given backpressure is active, when load subsides to below threshold, then p99 append latency returns to <= 500 ms within 2 minutes and throttling ceases. Given backpressure is active, when /metrics is queried, then metrics include ingest_rate, queue_depth, throttled_requests_total, and append_latency histograms.
UTC Timestamping and Order Guarantees
Given any event write, when persisted, then timestamp_utc is recorded in RFC3339 with Z suffix and reflects UTC time synchronized via NTP (no local offsets allowed). Given events within the same partition, when appended, then timestamps are non-decreasing and previous_hash links to the immediately prior committed event for that partition. Given node clock skew exceeds 100 ms from reference, when detected, then the node refuses to write and raises an alert until time is within tolerance.
Write-Once Durable Storage with Cross-Region Replication
Given an event that has been acknowledged (HTTP 2xx), when any update or delete API call is attempted, then it is rejected and the event remains immutable. Given a simulated primary-region outage during steady ingest, when failover occurs, then no acknowledged events are lost (RPO=0 for acknowledged writes) and ingestion resumes in the secondary region within 5 minutes (RTO <= 5 minutes). Given a writer node crashes mid-write, when it restarts, then WAL/recovery ensures no partial or torn events are visible and the chain remains consistent.
Periodic External Anchoring and Proof Retrieval
Given an anchoring interval of 15 minutes, when the anchor job runs, then it produces a proof (e.g., Merkle root and timestamp authority receipt) that commits to all finalized events in that interval and stores the proof with anchor_id and anchor_timestamp. Given a verification request with --require-anchor over an anchored period, when executed, then the response includes anchor_verified=true with anchor_id, anchor_timestamp, and proof; if no anchor covers the range, then anchor_verified=false with reason=no_covering_anchor. Given the external anchoring service is unavailable, when the job runs, then it retries with exponential backoff for up to 24 hours, emits alerts on failures, and does not block event ingestion.
Comprehensive Access Event Schema
"As a compliance officer, I want each log entry to capture who, what, when, where, and why, including approving owner and scopes, so that audits are complete and defensible."
Description

Define a versioned, extensible event schema capturing who, what, when, where, and why for every access action. Include actor identity, role, auth method, source (API/UI), client/app, IP, and device; resource identifiers for schedulers, calendars touched, and meeting objects; timestamps (UTC) with original user timezone, duration, and correlation/request IDs; action type and result; scopes/permissions evaluated; justification text and reference (ticket/change request); and approving owner identity with approval timestamp and evidence link. Support field-level redaction for sensitive values (e.g., meeting titles) and normalization/enrichment (team, project, region). Maintain backward/forward compatibility and reject or quarantine malformed events with diagnostics.

Acceptance Criteria
Actor and Source Identity Captured
Given an access action is performed When the event is emitted to the Access Ledger Then event.actor.id is a valid UUIDv4 and references an existing directory principal And event.actor.role is one of {admin, owner, manager, member, service} And event.auth.method is one of {oauth, saml, oidc, api_key, passwordless, mfa} And event.source.channel is one of {API, UI} And event.client.app_id is a non-empty string <= 128 chars And event.network.ip is a valid IPv4 or IPv6 string And event.device.fingerprint is a 64-character lowercase hex SHA-256 And no required field is null or empty And on validation failure the event is quarantined with code EVT_ACTOR_FIELDS_MISSING and reasoned diagnostics
Resource Identifiers Logged for Schedulers, Calendars, Meetings
Given an access action touches scheduling resources When the event is emitted Then event.resources.scheduler.id is present as UUIDv4 And event.resources.calendars is an array containing every unique calendar touched with each item including provider in {google, outlook, ics}, calendar_id (provider-native id), and access_scope in {read, write, admin} And if meeting objects are touched, event.resources.meetings is an array with each item including meeting_id (UUIDv4), external_id (provider-native id), and title (which may be redacted per policy) And for multi-calendar operations the count of event.resources.calendars equals the set of calendars invoked by backend calls And on missing or malformed resource identifiers the event is quarantined with code EVT_RESOURCE_INVALID
Timestamp, Timezone, Duration, and Correlation Metadata
Given an access event is generated When it is accepted by the ledger Then event.ts_utc is RFC3339 with Z suffix (e.g., 2025-09-03T12:34:56Z) And event.user_tz is a valid IANA timezone (e.g., America/Los_Angeles) And event.duration_ms is an integer >= 0 And event.correlation_id and event.request_id are UUIDv4 or ULID and are identical across all events in the same request chain And events produced in a synthetic multi-step test (>= 3 events) share the same correlation_id And invalid timestamp or timezone values cause quarantine with code EVT_TIME_INVALID
Action Type, Result, and Scope Evaluation Recorded
Given an access attempt occurs When policy evaluation completes Then event.action.type is one of {create, read, update, delete, list, invite, revoke, export} And event.result is one of {success, denied, error, partial} And event.evaluated_scopes is an array where each item includes scope (string), decision in {allow, deny}, and policy_id (string) And for any event.result = denied, at least one evaluated_scopes item has decision = deny and event.denial_reason is populated And permission_model_version is present as semver (e.g., 1.4.0)
Justification and Approval Linkage for Elevated Actions
Given an action requires elevated scope per configuration When the event is emitted Then event.justification.text is present and length >= 10 characters And event.justification.reference matches pattern ^(JIRA|SNOW|CHANGE)-[A-Z0-9]+-\d+$ or a configured regex And event.approval.owner.id is present (UUIDv4) and owner_role in {owner, admin} And event.approval.timestamp is RFC3339 Z and event.approval.evidence_url is a valid https URL And if approval is missing, event.result = denied and event.denial_reason = approval_missing And if approval is present, event.result ≠ denied due to approval_missing
Field-Level Redaction and Metadata Enrichment
Given sensitive fields are configured for redaction (e.g., resources.meetings[].title, attendees) When an event is stored or exported Then sensitive values are replaced with a token or salted hash consistently per tenant and never persisted in plaintext And event.redaction.fields lists every redacted field with policy_id and reason And unredacted retrieval requires scope ledger.unredact; otherwise redacted values remain in all outputs And normalization maps codes and names to canonical forms: region in {AMER, EMEA, APAC}, team and project are lower_snake_case, unknown values map to "unknown" And enrichment adds team, project, and region from directory or mapping service when available without blocking ingestion; missing enrichments do not fail the event
Schema Versioning, Compatibility, and Malformed Event Quarantine
Given the event schema is versioned When an event is ingested Then event.schema.version follows semver MAJOR.MINOR (patch optional) And readers must successfully parse events within the same MAJOR and any MINOR >= current-1 to current+1, preserving unknown fields under event.extensions And within a MAJOR, breaking changes are disallowed; required fields cannot be removed and existing field types cannot change And malformed events (missing required or type-mismatched fields) are quarantined with diagnostics including code, field, and reason; none reach the primary ledger store And quarantine metrics (quarantine_count, by_code) are emitted and a reprocess path exists to replay after fixes
Audit Console: Search, Filter, and Saved Reports
"As an engineering manager, I want to quickly find all calendar modifications made to Team A’s schedulers last week so that I can investigate an incident efficiently."
Description

Deliver an in-app console to search and analyze the access ledger at scale. Provide fast, faceted filters by user, team, scheduler, calendar, action, scope, result, time range, IP, country, and approval status; free-text search across justifications; sortable columns; pagination; and row expansion to inspect full event JSON. Support absolute and relative time filters with timezone-aware display. Allow saved searches, reusable report templates, shareable deep links respecting RBAC, and export of current results to CSV. Optimize for large datasets with efficient indexing and query timeouts.

Acceptance Criteria
Faceted Filtering by User/Team/Scheduler/Calendar/Action/Scope/Result/IP/Country/Approval
Given a ledger with events across multiple users, teams, schedulers, calendars, actions, scopes, results, IPs, countries, and approval statuses When the analyst selects multiple facets simultaneously (e.g., user=U1 AND team=T2 AND action=READ AND result=DENIED AND country=DE) and applies a time range Then the result set contains only events satisfying all selected facets (logical AND) and the time range And the first page of results returns within 2 seconds at p95 for queries yielding up to 1,000,000 matched events And queries exceeding 10 seconds return a timeout error without partial results, preserving the selected filters And clearing an individual facet immediately updates the results to reflect the remaining active filters
Free-Text Search on Justifications
Given events contain varying justification texts including phrases and special characters When the analyst enters a free-text query in the search box Then only events whose justification field contains the query substring (case-insensitive, literal match) are returned And the search is applied in conjunction with active facets and time filters And special characters are treated as literals (no errors, no unintended parsing) And the first page of results returns within 2 seconds at p95 for up to 1,000,000 matched events And clearing the query restores the prior faceted results
Time Filters and Timezone-Aware Display
Given the analyst selects a relative time filter of Last 24 hours When viewing event timestamps in the results Then all timestamps display in the analyst’s selected timezone (defaulting to profile timezone) with correct DST handling And the count of returned events equals the count produced by applying the equivalent absolute UTC range Given the analyst selects a custom absolute time range with start <= end When applying the filter Then only events with created_at within [start, end] are returned And the applied time range and timezone are preserved in the URL for deep links and on refresh
Pagination at Scale with Deterministic Ordering
Given a result set larger than one page When navigating Next/Previous or jumping to a specific page and changing page size Then no events are duplicated or skipped between adjacent pages for the same filter and sort state And the default ordering is deterministic with a stable tie-breaker so paging the same state yields identical items And columns are sortable (e.g., timestamp, user, action, result) with asc/desc toggles, and the chosen sort persists across pagination and deep links And page transitions complete within 2 seconds at p95
Row Expansion for Full Event JSON Inspection
Given a results table with access events When the analyst expands a row Then the full immutable event JSON is displayed exactly as stored, including who, what, when, why, scopes, calendars touched, approving owner, IP, and country And the row can be collapsed and re-expanded without changing the table’s filter, sort, or pagination state
Saved Searches, Report Templates, and RBAC-Respecting Deep Links
Given an analyst with permission to save reports When saving the current filter/search/sort/time configuration as a named saved search Then the saved search is available to the creator and shareable to specified roles/teams per RBAC And opening the saved search or its deep link restores the exact configuration and returns results limited by the viewer’s RBAC And owners can rename, update, and delete their saved searches; non-owners cannot modify unless granted And deleting a saved search invalidates its deep link And accessing a deep link without required permissions returns a 403-equivalent message
CSV Export of Current Results with Limits and Timeouts
Given a filtered and/or searched result set When the analyst exports to CSV Then the CSV contains only rows and columns permitted by RBAC and currently selected filters, includes a header row, uses UTF-8 encoding, and formats timestamps in ISO 8601 with timezone offset And exports up to 100,000 rows complete within 60 seconds at p95 without freezing the UI And if the export query exceeds the 10-second query timeout, the export fails with a clear timeout message and no partial file is downloaded And the file name includes the saved search/report name (if any) and UTC timestamp of export
SIEM/CSV Export and Streaming Connectors
"As a security analyst, I want to export access logs to our SIEM on an hourly schedule so that centralized detection rules can run without manual steps."
Description

Enable on-demand and scheduled export of access logs to CSV and JSONL, plus real-time delivery via webhooks or streaming connectors to external monitoring tools. Provide schema mapping, field selection, filtering by time and attributes, and configurable export windows. Implement incremental, cursor-based exports with at-least-once delivery, idempotent event IDs, retry with exponential backoff, batching, compression, and signed requests. Include backfill for historical ranges, export job monitoring with success/failure metrics, rate limits, and an audit trail of export actions.

Acceptance Criteria
On-Demand CSV/JSONL Export with Field Selection and Attribute Filters
Given a user with Export Access Logs permission selects a file format (CSV or JSONL), a time range, field selection, and attribute filters (actor_id, action, scope, calendar_id) When they trigger an on-demand export Then the system returns a downloadable file containing only matching records and only selected fields, with timestamps in UTC RFC3339 and a stable globally unique event_id per record And the record count in the file exactly equals the number of records matching the filters And the file is generated within 60 seconds for up to 1,000,000 records; otherwise the user is prompted to paginate or schedule And the download link is access-controlled and valid for 24 hours
Scheduled Exports with Configurable Time Windows and Deduplication
Given a scheduled export configured with cron/interval, time window (e.g., last 24h), filters, and file format When the schedule triggers Then a new export artifact is produced containing only records within the window and filters And overlapping windows do not duplicate records due to event_id-based deduplication And each run records run_id, start_cursor, end_cursor, file URI(s), record_count, and status in job history And failures are retried up to 5 times with exponential backoff before marking the run as Failed And configuration changes take effect on the next run and are audit-logged
Incremental Cursor-Based Export with Idempotency Guarantees
Given an export job configured for incremental cursoring on (timestamp, event_id) When the job runs and no new records exist beyond the saved cursor Then zero records are exported and the cursor remains unchanged When new records exist Then only records with (timestamp, event_id) greater than the saved cursor are exported and the cursor advances atomically after successful completion And re-running the same job with the same cursor never emits duplicates due to idempotent event_id and deduplication logic And the current and previous cursor values are retrievable via API
Real-Time Webhook/Streaming Delivery with At-Least-Once and Signed Requests
Given a configured streaming destination (webhook or connector) with shared secret, compression enabled, batch size/time thresholds, and max in-flight batches When access events occur Then events are delivered within 5 seconds on average (p95 ≤ 15s) as HTTP POSTs (or connector messages) containing event_id, sequence, and payload signed with an HMAC-SHA256 signature header And on 5xx or timeout, deliveries are retried with exponential backoff (1s, 2s, 4s … capped at 5m) for up to 24 hours, ensuring at-least-once delivery; duplicates carry the same event_id for idempotent processing And batches do not exceed 1,000 events or 5 MB and are gzip-compressed when enabled And 4xx responses other than 429 permanently fail the batch and surface error details in monitoring
Schema Mapping and Field Selection Validation
Given a mapping configuration that renames and selects fields When an export or stream runs Then output fields are present exactly as mapped; required fields (event_id, timestamp, action) are always included And unknown/invalid field names cause configuration validation to fail with a clear error before execution And nested attributes (scope, calendars) are flattened consistently (dot notation in CSV; arrays/objects preserved in JSONL) And a dry-run validation endpoint returns 200 with up to 10 sample transformed records
Historical Backfill with Batching, Compression, and Resume
Given a backfill request specifying a historical date range and output format/compression When the backfill executes Then the system partitions the range into pages of ≤ 100,000 records or 5 GB per file, names files with ISO8601 start_end timestamps, and compresses files per selection (gzip or zstd) And each page is retried independently with exponential backoff; successful pages are not re-emitted on retry And the backfill respects tenant rate limits and does not degrade real-time streaming SLAs And the job can be paused and resumed via API without duplicating completed pages
Export/Stream Monitoring, Metrics, Rate Limits, and Audit Trail
Given export and streaming jobs are running When querying the monitoring API/UI Then each job shows status (Running, Success, Failed, Paused), time range, cursor, record_count, bytes_sent, retries, throttle_time, and latency p50/p95 And tenant-level rate limits are enforced (max 10 concurrent exports; max 10,000 events/sec outbound); when exceeded, jobs are throttled (429) and automatically back off without data loss And all export configuration changes and job actions are recorded in the audit trail with actor, what, when, why, scopes/calendars touched, and approving owner And metrics are retained for at least 30 days and are exportable as CSV/JSON
Role-Based Access and Redaction Controls
"As an org admin, I want to restrict who can view or export sensitive access logs and ensure PII is masked by default so that we comply with privacy policies."
Description

Implement fine-grained RBAC for the Access Ledger, controlling who can view, search, export, and manage retention. Support scoping by organization, team, scheduler, and calendar, with least-privilege defaults. Provide configurable PII masking and field-level redaction in UI, APIs, and exports, with secure reveal via just-in-time authorization and audit. Require elevated approval for bulk exports and watermark downloaded files with requester identity and timestamp. Log all ledger access actions for meta-auditing, and enforce data residency constraints where applicable.

Acceptance Criteria
Scoped RBAC for Viewing and Searching the Access Ledger
Given a user with Viewer role scoped to Organization O, Team T, Scheduler S, and calendars [C1, C2] When they search the Access Ledger with filters outside their scope (e.g., Team T2, calendar C3) Then the system returns HTTP 403 for API or a "not authorized" UI state and no data from outside scope is revealed Given the same user When they search within their scope and apply filters by scheduler, team, calendar, time range, and action type Then only entries matching the filters within [O, T, S, C1, C2] are returned and no out‑of‑scope entries are present Given a new user with no explicit permissions When they attempt to access the Access Ledger Then access is denied by default (least‑privilege) with HTTP 403 or UI access blocked
Standard Export Controls with Role Scope and Watermarking
Given a user with Exporter role scoped to Organization O Team T When they export search results to CSV Then the file contains only fields and rows permitted by the user's role and scope And every file is watermarked with requester identity (user id or email) and timestamp in header/footer and embedded metadata And a single‑use, time‑limited download link is issued (expires per org policy, default ≤ 24 hours) And the export event is logged with who/what/when/why and applied filters Given a user without export permission When they attempt any export Then the export is blocked (HTTP 403) and the UI export controls are disabled
Bulk Export Requires Elevated Approval and Watermarked Output
Given a user with Exporter role When they request an export that meets bulk criteria (≥ 10,000 rows OR time range > 30 days OR includes more than one team) Then the request enters Pending Approval and requires approval by an Organization Owner or Security Admin within 7 days, with approver reason captured Given approval is granted When the export is generated Then the output is watermarked with both requester and approver identities and approval timestamp And the approval and export are fully audited (who/what/when/why, filters, row count) Given approval is denied or expires When the requester revisits the export request Then the export is not generated and the requester is notified of the outcome Given a user initiates multiple exports within 1 hour that cumulatively meet bulk criteria When the system detects the cumulative threshold is exceeded Then elevated approval is required before further exports proceed
PII Masking and Field-Level Redaction with Just-in-Time Reveal
Given redaction defaults are enabled When any user views or queries the Access Ledger Then PII fields (e.g., full name, email, IP address) and sensitive free‑text "why" notes are masked in UI, APIs, and exports unless explicitly revealed Given a user with PII_View permission initiates a JIT reveal for a specific record and field When they provide a reason, complete MFA, and obtain approver authorization Then the selected fields are revealed for that user only, limited to the specified records, for a maximum of 15 minutes And the reveal is fully audited with requester, approver, reason, fields revealed, scope, and duration Given an export is generated without JIT approval When PII is present in the result set Then PII remains masked or tokenized in exported files and SIEM streams
Retention Management Restricted by Role and Scope with Audit
Given a user with Retention_Admin role scoped to Organization O When they create or update a retention policy for the Access Ledger (e.g., 365 days for Team T) Then the policy applies only within the user's scope and cannot shorten retention below the organization's minimum policy And a reason for change is required and approver confirmation is enforced if org policy requires approval And the change is logged with before/after values, who/when/why, approver (if any), and impacted scopes Given a user without Retention_Admin role When they attempt to view or modify retention settings via UI or API Then modification controls are hidden/disabled in UI and API returns HTTP 403 for updates
Meta-Audit Logging of All Ledger Access Actions
Given any access to the Access Ledger occurs (view, search, export, JIT reveal, retention change) When the action completes or fails Then an immutable meta‑audit record is appended capturing requester identity, role, scopes, action type, parameters/filters, result (success/failure), row count or bytes exported, client IP, user agent, request id, watermark id (if applicable), and approver details (if any) And attempts to alter or delete meta‑audit records are blocked (HTTP 403) and the attempt is itself logged
Data Residency Enforcement for Access and Exports
Given an organization with EU‑only data residency When users query or export Access Ledger data Then data is served and stored only from EU‑designated infrastructure, and cross‑region export destinations are blocked with HTTP 403 And SIEM integrations must target EU endpoints; mismatched destinations are rejected And all residency enforcement decisions are logged with who/what/when and requested destination/region
Retention, Archival, and Legal Hold
"As a legal counsel, I want to place a legal hold on access logs for a specific investigation so that records are preserved regardless of standard retention."
Description

Provide configurable retention policies per organization and event type, with automatic transition to archival storage after a defined period and eventual deletion. Support legal hold to suspend deletion for specified scopes and time ranges, with an auditable changelog of policy edits and holds. Include on-demand restoration from archive into a read-optimized store, lifecycle cost controls, and encryption at rest with key rotation. Expose policy previews to estimate impact before changes are applied and surface deletion job receipts for compliance evidence.

Acceptance Criteria
Retention Policy Configuration Per Org and Event Type
Given I am an org admin with Retention:Manage permission When I create a retention policy with event_types, archive_after_days, retention_days, and justification Then the system validates retention_days > archive_after_days >= 0, event_types exist, and justification is provided And the policy is saved with a unique ID, version, scope, approver_required flag, and effective_at timestamp And creation is rejected if another active policy overlaps the same org and event_types And computing the effective policy for any record yields exactly one policy (most-specific scope > org default > system default)
Lifecycle Transitions: Archival and Deletion Jobs
Given records exceed archive_after_days and are not on legal hold When the archival job runs Then matching records are moved to archival storage and annotated with archived_at, storage_class, and policy_id And job metrics (counts archived, counts skipped, error count) are recorded with job_id and exposed to admins Given records exceed retention_days and are not on legal hold When the deletion job runs Then matching records are permanently deleted And a signed deletion receipt is produced containing job_id, org_id, policy_id, time_window, counts_deleted, counts_skipped_by_hold, checksum, and completed_at And the receipt is viewable in the UI and exportable to CSV and SIEM
Legal Hold by Scope and Time Range
Given I am a compliance user with LegalHold:Manage permission When I create a legal hold with scope (org/scheduler_ids/calendar_ids/event_types), time_range, reason, and approver Then the hold is validated, activated immediately, and assigned a unique ID with created_by and approved_by And archival and deletion jobs skip matching records while the hold is active And attempts to delete held records via API or UI return 403 with error code LEGAL_HOLD_ACTIVE and are logged Given a hold has an end_at When end_at passes Then matching records become eligible for lifecycle jobs on the next run
Auditable Changelog for Policies and Legal Holds
Given a create, update, or delete of a retention policy or legal hold When the action is committed Then an immutable, append-only changelog entry is recorded with who, what, when, why, before/after values, approver, scope, request_id, source_ip, and hash_chain_id And entries are searchable and filterable by actor, date range, object type, and scope And entries are exportable to CSV and streamable to SIEM And changelog integrity is verifiable by recomputing the hash chain
On-Demand Restoration from Archive
Given an authorized user requests restoration with filters (time_range, event_types, scheduler_ids/calendar_ids) When the request is submitted Then a restore task is created with states requested -> in_progress -> available -> expired and a progress percentage And matching archived records are materialized into the read-optimized store as read-only with restored_at and restore_request_id And restored data is automatically re-archived upon expiration TTL without modifying original archived copies And all steps are logged in the Access Ledger and are exportable
Policy Preview with Lifecycle Cost Controls
Given a pending edit to a retention policy When the user clicks Preview Then the system shows estimated record counts affected, data volume to archive/delete, projected monthly storage cost deltas by storage class, and the next job run time And records under legal hold are excluded from counts and labeled as excluded And the preview can be exported to CSV and includes the query fingerprint and preview_hash Given cost budgets and alerts are configured When the preview projects a budget exceedance Then the UI displays a blocking warning and requires explicit acknowledgment before allowing Save And the applied change must reference the preview_hash in the changelog entry
Encryption at Rest and Key Rotation
Given any retention, archival, restoration, or deletion operation writes or reads data When the data is stored in any tier Then it is encrypted at rest using KMS-managed keys and the object metadata records key_version And scheduled or on-demand key rotation can be executed without downtime And after rotation, previously stored data remains decryptable, new data uses the new key_version, and rotation events are logged with approver and reason
Approval Evidence Capture and Correlation
"As a security lead, I want each privileged access event to record the approving owner and evidence link so that we can prove proper authorization during audits."
Description

Ensure privileged or sensitive actions record the approving owner and link to verifiable evidence. Ingest approval metadata (approver identity, method, timestamp, justification, external ticket URL) either inline or via correlation IDs that tie requests, approvals, and actions across systems. Flag and alert on actions missing required approval context. Display approval details in the audit console and include them in exports. Validate approver identity via the organization’s identity provider and preserve signatures alongside events for end-to-end traceability.

Acceptance Criteria
Inline Approval Metadata Capture on Privileged Action
Given a privileged action requires approval When the caller submits inline approval metadata with fields approver_id, approver_display_name, method, timestamp_iso, justification, external_ticket_url Then the ledger records an immutable event within 5 seconds containing who/what/when/why, scopes, calendars_touched, approving_owner, and the provided approval fields And the event is assigned a unique event_id and write-once hash visible in the audit console And missing or malformed required fields (empty approver_id, invalid ISO8601 timestamp, non-http(s) ticket URL) result in HTTP 400 and no event write
Correlation ID Linking Across Systems
Given a correlation_id is supplied on request, approval, and action events When approval and action events arrive in any order within a 24-hour window Then the ledger links them into a single trace via correlation_id and displays the linkage in the audit console And orphaned action events lacking a matching approval are auto-flagged "Missing Approval" and become linked if a matching approval arrives later And correlation_id must be UUIDv4; nonconforming values are rejected with HTTP 400
Alerting on Missing or Invalid Approval Context
Given policy requires approval for privileged actions When an action event lacks approval context or has invalid context (unverified approver, expired approval >24h, mismatched correlation_id) Then the system flags the event severity=high, displays a "Missing/Invalid Approval" badge, and emits an alert to configured channels (email and webhook) within 60 seconds including event_id, actor, scopes, calendars_touched, and reason And alert deduplication suppresses repeat alerts for the same event_id for 24 hours
Audit Console Approval Details and Filtering
Given an auditor views the Access Ledger When opening an event with approval evidence Then the details show approver identity (name and unique ID), method, timestamp (original timezone and localized), justification text, external ticket URL (clickable), IdP validation status, signature verification status, scopes, and calendars_touched And the auditor can filter by approver_id, method, time range, correlation_id, missing_approval=true, and ticket URL substring, with results returning under 2 seconds for up to 50,000 events
CSV and SIEM Export with Approval Evidence
Given an auditor exports events via CSV and via SIEM push When exporting a filtered set Then each record includes approver_id, approver_display_name, method, timestamp_iso, justification, external_ticket_url, correlation_id, idp_validation_result, signature_verification_result, approving_owner, scopes, and calendars_touched And CSV conforms to RFC4180 (UTF-8, quoted fields, escaped delimiters) and downloads within 60 seconds for 10,000 events And SIEM payloads are JSON with stable field names and delivered with at-least-once semantics, retrying for up to 24 hours on failure
Approver Identity Validation via Identity Provider
Given an approval is submitted with approver identity When validating with the organization’s IdP Then the approver is verified as an active user with a unique subject (sub) and authorized per configured groups/roles; the IdP issuer, assertion_id, and authentication_time are recorded on the event And approvals where approver == requestor are rejected unless a self-approval policy is explicitly enabled; rejections are logged and flagged
Signature Capture, Preservation, and Re-Verification
Given an approval includes a digital signature (e.g., JWS/JWT) with key metadata (kid, alg) When the event is ingested Then the raw signature and metadata are stored immutably alongside the event, a verification is performed against the IdP/public key and the result persisted, and a tamper-evident hash chain is updated And auditors can re-verify the signature on demand in the audit console and via API with the result returned in under 2 seconds for a single event

Anomaly Alerts

Detect scope creep, unusual after-hours access, or cross-team calendar touches outside policy. Real-time Slack/Email alerts include quick fixes (revoke token, tighten scope) so admins intervene before risk grows.

Requirements

Real-Time Anomaly Detection Engine
"As a workspace admin, I want anomalies detected in real time so that I can intervene before risks escalate or disrupt distributed teams."
Description

Continuously evaluates event streams (calendar changes, access logs, token scopes) to detect scope creep, unusual after-hours access (timezone-aware), and cross-team calendar touches that violate policy. Combines rule-based checks with behavioral baselines to minimize false positives, assigns severity and reason codes, and de-duplicates related signals. Integrates with TimeTether’s scheduling context (work windows, fairness rotation, team mappings) to improve accuracy, and supports alert throttling and correlation across users and teams.

Acceptance Criteria
Timezone-Aware After-Hours Access Alert
Given a user with a configured home timezone and work window 09:00-17:00 on weekdays And the tenant has Slack and Email alert destinations configured When the user accesses a protected calendar resource at 22:13 local time on a workday Then the engine emits an alert within 30 seconds with severity=High and reason_code=AFTER_HOURS_ACCESS And the alert payload includes user_id, team_id, local_timestamp, policy_id, and suggested_actions=[tighten_work_window] And the alert is delivered to both Slack and Email destinations
OAuth Scope Creep Detection and Remediation
Given an OAuth token for app X on user Y has approved scopes S_old per policy When the token gains additional scopes S_new that exceed policy (e.g., calendar.write added) Then the engine emits an alert within 30 seconds with severity=Critical and reason_code=SCOPE_CREEP And the alert payload includes token_id, client_id, diff_of_scopes, requesting_ip, user_id, policy_id And the alert presents quick_actions=[revoke_token, restrict_scopes_to_policy] And subsequent scope additions for the same token within 15 minutes do not create new alerts but increment occurrence_count on the existing alert
Cross-Team Calendar Touch Outside Policy
Given team mappings state Team A cannot edit Team B calendars except via service account SA1 When a Team A user updates a Team B calendar event using a personal token Then the engine emits an alert within 30 seconds with severity=High and reason_code=CROSS_TEAM_TOUCH And the alert includes acting_user_id, target_calendar_id, team_from, team_to, token_type, policy_id, event_id And the alert suggests quick_actions=[revoke_token, enforce_service_account_only]
Composite Risk Scoring with Behavioral Baseline
Given a user has at least 14 days of baseline behavior captured And baseline after-hours access rate is <= 1 per 7 days When the user records >= 4 after-hours accesses in the last 24 hours Then the engine computes composite_risk_score >= 70 combining rule-based and behavioral signals And emits an alert with reason_code=BEHAVIORAL_DEVIATION including baseline_metrics and contributing_factors And no alert is emitted if an approved exception window covers the accesses
Alert Throttling and De-duplication
Given multiple AFTER_HOURS_ACCESS anomalies occur for the same user within a 15-minute window When the second and subsequent events are detected Then the engine does not emit new alerts And updates the existing alert group with incremented occurrence_count and latest_event_timestamp And sends no more than 1 notification per destination per alert group within the window
Cross-User Correlation Into Incident
Given three or more users in the same tenant trigger SCOPE_CREEP alerts referencing the same client_id within 10 minutes When correlation rules execute Then the engine creates a correlated incident with reason_code=CORRELATED_SCOPE_CREEP and severity=Critical And the incident references related alert_ids, affected_user_ids, and client_id And only one incident is created per client_id per 30-minute window
Scheduling Context-Aware Suppression
Given a TimeTether fairness-rotation meeting pushes a user outside normal work hours and is marked approved_after_hours=true When related access events occur within the approved window Then the engine suppresses AFTER_HOURS_ACCESS alerts for those events And records a suppressed_alert entry with reason_code=APPROVED_AFTER_HOURS and meeting_id And generates normal alerts if accesses occur outside the approved window
Secure Data Ingestion & Normalization
"As a platform engineer, I want reliable and secure ingestion and normalization of event data so that anomaly detection remains accurate and consistent across providers."
Description

Establishes resilient, secure connectors to Google Workspace, Microsoft 365, Slack, and internal logs to ingest calendar events, permission scopes, token activity, and user/team metadata in near real time via webhooks with polling fallbacks. Normalizes data into a unified schema enriched with timezone, work-window policies, and team relationships. Handles backfill, deduplication, replay, rate limits, and idempotency with at-least-once delivery guarantees and full encryption in transit and at rest. Provides configurable retention, consent controls, and PII redaction aligned with TimeTether’s data model.

Acceptance Criteria
Real-Time Webhook Ingestion with Polling Fallback
Given active subscriptions to Google Workspace, Microsoft 365, Slack, and internal logs, when any relevant event is produced, then 95th percentile end-to-end ingestion latency is <= 30 seconds and 99th percentile is <= 60 seconds. Given a webhook outage or subscription expiration, when the system detects failed deliveries, then polling is activated within 2 minutes and maintains data lag <= 5 minutes until webhooks are restored. Given approaching subscription expiration, when the remaining time is <= 24 hours, then subscriptions are auto-renewed with zero data loss and no duplicate events. Given telemetry, when observed over a rolling 24-hour window, then successful delivery rate is >= 99.9% with error budget reported.
Idempotent Deduplication and Replay Safety
Given duplicate or out-of-order event deliveries, when events share the same source event ID and version, then the normalized store contains exactly one current record and zero duplicate rows. Given a replay of the last 24 hours of events, when reprocessing completes, then system state is unchanged except for audit timestamps and metrics counters. Given concurrent deliveries for the same entity, when racing inserts occur, then transactional constraints prevent double writes and the final state is deterministic. Given an at-least-once delivery model, when a consumer restarts mid-batch, then reprocessing produces no side effects beyond idempotent upsert.
Unified Schema Normalization and Enrichment
Given raw connector payloads, when normalized, then each record includes source, external_id, actor_id, resource_id, action, scopes, occurred_at, received_at, tenant_id, and connector metadata per the TimeTether schema. Given user and team metadata, when enrichment runs, then records include resolved timezone, effective work-window policy, and team relationships for both actor and resource. Given validation rules, when a required field is missing or malformed, then the record is quarantined with a machine-readable error code and is excluded from downstream consumers. Given unknown fields, when encountered, then they are preserved in an extensible attributes bag without breaking schema contracts.
Scalable Backfill Under Rate Limits
Given a tenant-initiated 90-day backfill for 10,000 users, when executed, then the job completes within 12 hours while keeping 99th percentile real-time webhook latency <= 60 seconds. Given connector rate limits, when limits are reached, then exponential backoff with jitter is applied and no requests are sent above provider thresholds. Given a backfill interruption, when the job resumes, then it continues from the last successful checkpoint without duplicating data. Given backfill completion, when validation runs, then completeness checks (counts or hashes) match provider totals within 0.5%.
End-to-End Encryption and Key Rotation
Given data in transit between connectors and the ingest service, when inspected, then TLS 1.2+ with strong ciphers is enforced and plaintext traffic is rejected. Given data at rest in primary and derived stores, when verified, then AES-256 encryption is enabled with keys managed in a hardware-backed KMS. Given a KMS key rotation, when a new key version is activated, then read/write operations continue without downtime and all new writes use the new key version immediately. Given rotation completion, when background re-encryption runs, then previously stored sensitive data is re-encrypted to the new key within 7 days and audit logs record the event.
Retention Configuration and Erasure
Given an admin-configured retention period of N days, when records exceed N days of age, then they are purged from primary and derived stores within 24 hours, with deletions logged by tenant and dataset. Given a legal hold on specified datasets, when retention jobs run, then purges are skipped for held data until the hold is lifted. Given a subject erasure request for a user, when processed, then all records keyed to that subject are deleted from all stores and caches within 7 days, and the operation is idempotent. Given analytics/search indices, when erasure completes, then references to the subject are removed and cannot be retrieved by identifier or content search.
Consent Controls and PII Redaction
Given tenant consent settings, when connector authorization is initiated, then only approved scopes are requested and authorization fails closed if consent is absent. Given ingestion of calendar events and token activity, when processing, then configured PII fields are redacted or tokenized before persistence to the normalized store. Given redaction policies, when toggled, then changes take effect for new data within 5 minutes and can be retroactively applied to historical data via a reprocessing job with progress tracking. Given anomaly detection features, when redaction is enabled, then required derived features remain available without exposing raw PII.
Policy Rules & Threshold Configuration
"As an IT policy owner, I want to configure detection rules and thresholds so that alerts match our policies and avoid unnecessary noise."
Description

Provides an admin UI and API to define and manage policies for after-hours access by timezone, cross-team calendar touch boundaries, scope limits, and exception windows (holidays, launches). Supports per-team overrides, versioned rule changes with audit history, dry-run/simulation to preview alert impact, and importable templates. Aligns with TimeTether’s fairness-driven rotation and work-window settings to ensure policies reflect operational norms while reducing alert noise.

Acceptance Criteria
Configure After-Hours Access Policy by Timezone
Given an admin in the Policies UI or via API, When they create or edit a Global after-hours rule with timezone-specific work windows, Then the system validates each timezone has defined work-window settings and blocks save with actionable errors if missing. Given team-level overrides are enabled, When an admin creates a Team override, Then inherited fields are pre-populated from Global and only changed fields are marked as overridden with a visible diff indicator. Given a published after-hours policy, When the engine evaluates an event at a time outside the participant’s configured work window in their local timezone, Then the event is classified as after-hours per the active rule and logged with policy version and rule ID. Given API parity, When the rule is created via API with valid payload, Then the API returns 201 with the new rule ID and subsequent GET returns the persisted configuration including effective scopes and inheritance metadata. Given a draft after-hours change, When the admin attempts to publish with invalid overlaps (e.g., contradictory windows), Then publish is blocked and conflicts are listed by timezone and team.
Define Cross-Team Calendar Touch Boundaries
Given Teams A and B exist, When an admin sets a boundary rule "A may touch B calendars = Deny" and adds an exception actor group "Incident Managers", Then evaluations deny touches from A to B except for actors in the exception group and this is reflected in simulation results and live anomaly classification. Given a Global boundary exists, When Team C defines a team-specific override, Then the Team C override takes precedence for members of Team C while others continue to use the Global rule. Given a boundary rule is saved, When validated, Then the system ensures all referenced teams and groups exist and rejects the save with specific error messages for unknown identifiers. Given a boundary is published, When a service account attempts to create or modify an event on a prohibited team’s calendar, Then the anomaly record includes boundary rule ID, matched condition, and suggested remediation link.
Set Scope Limits for Integrations and Service Accounts
Given an admin defines allowed scopes and activity thresholds (events touched/hour, calendars accessed/day) for an integration token, When the token’s activity exceeds any threshold, Then the next evaluation marks it in violation and generated alerts include quick-fix suggestions (Revoke token, Tighten scopes to listed minimum) in Slack/Email payloads. Given a scope rule references provider-specific scopes, When the scopes are unknown for the selected provider, Then the system blocks save with a list of invalid scopes and links to supported scopes. Given a token is switched to Dry-Run mode in its scope rule, When violations occur, Then no live alerts are sent and counts appear only in simulation metrics tagged as Dry-Run. Given an API PUT updates a token’s scope limits, When successful, Then GET returns the updated limits and audit records reflect the actor, timestamp, and diff.
Configure Exception Windows (Holidays, Launches)
Given an admin creates an exception window with start/end, timezone, and target teams, When saved and published, Then evaluations within that window suppress otherwise matching after-hours and cross-team boundary violations for the targeted teams. Given overlapping exception windows exist, When evaluations occur, Then the most specific (team-scoped over global) and narrowest time window takes precedence and is recorded on the event evaluation. Given an exception window end time is in the past, When attempting to publish, Then the system prevents publish and prompts to archive or adjust dates. Given an exception window is active, When viewing current policy state, Then the UI and API both list the window with remaining duration and affected policies.
Versioned Rule Changes with Audit History & Rollback
Given any policy change is saved (create/update/delete), When committed, Then a new immutable version is created with version ID, timestamp, actor, and machine-readable diff across rules. Given an admin opens Audit History, When selecting a prior version, Then the UI displays the full effective policy at that version and enables Rollback, which creates a new current version identical to the selected version without altering the historical record. Given an API consumer requests /policies/versions?from=...&to=..., When results are returned, Then they are paginated and include signer/actor metadata and checksums for integrity verification. Given a rollback is performed, When evaluations run, Then they reference the new current version ID and the change is visible in audit within seconds.
Dry-Run/Simulation to Preview Alert Impact
Given an admin has draft policy edits, When they run Simulation for the last 30 days, Then the system returns total projected alerts by rule, top affected teams/users, and delta versus the current active policy. Given the admin toggles inclusion of exception windows and fairness/work-window alignment, When re-running Simulation, Then the results reflect the selected toggles and note assumptions used. Given Simulation results are displayed, When the admin clicks Export, Then a CSV and JSON export is generated containing per-event evaluation outcomes, rule IDs, and version used. Given Simulation is run via API with a draft policy payload, When completed, Then the response includes a run ID that can be polled for status and later retrieved with metrics and samples.
Import Policy Templates and Map to Teams
Given an admin uploads a JSON or YAML policy template, When validation passes, Then a draft policy set is created and any placeholders (e.g., TEAM_ALPHA, TZ_EMEA) require mapping before publish. Given the mapping step, When the admin resolves all placeholders to existing teams, scopes, and timezones, Then the system allows publish; if unresolved, publish is blocked with a list of missing mappings. Given a template includes unsupported rules for this tenant, When importing, Then the system rejects import with specific line/field errors and a link to documentation. Given an API import with an idempotency key is retried, When identical payload is submitted, Then only one draft is created and the same import ID is returned. Given a template is published, When viewing audit, Then the created version notes the source template name and checksum.
Contextual Slack and Email Alerts with Quick Actions
"As an on-call admin, I want actionable alerts in Slack/Email so that I can remediate issues immediately without switching tools."
Description

Delivers real-time alerts to Slack (channels/DMs) and email containing concise context (who, what, when, policy violated, severity, recent related activity) and one-click actions (revoke token, tighten scope, snooze, mark as expected, open incident). Enforces RBAC and step-up confirmation for sensitive remediations, includes signed deep links to the triage view, and implements rate limiting, retries, and delivery telemetry. Honors user notification preferences and quiet hours while ensuring critical alerts reach on-call responders.

Acceptance Criteria
Slack DM/Channel Alert: Contextual Payload and Timeliness
Given an anomaly is detected and routing resolves to a Slack destination When the alert is sent Then it is delivered to the resolved Slack channel or DM within 10 seconds at the 95th percentile from anomaly ingestion And the message includes: who (actor/account), what (event type), when (ISO-8601 with timezone), policy violated, severity, and the last 5 related activities within the past 24 hours And the message contains action buttons: Revoke Token, Tighten Scope, Snooze, Mark as Expected, Open Incident And timestamps render in the recipient’s Slack profile timezone when available
Email Alert: Context, Deep Link, and Accessibility
Given an anomaly is detected and routing resolves to email recipients When the system composes the email Then the subject includes severity and policy name And the body includes who, what, when, policy violated, severity, last 5 related activities within 24 hours, and environment (e.g., prod/staging) And both HTML and plain-text parts are present And a deep link to the triage view is included, signed (HMAC-SHA256), with aud bound to the recipient and exp of 30 minutes And clicking a valid link opens the triage view as the recipient; expired or invalid links return 403 with a re-request access option And all times display in the recipient’s preferred timezone or org default
One-Click Quick Actions with Step-Up and RBAC
Given an authorized admin views the alert in Slack or email When they click Revoke Token or Tighten Scope Then the system requires step-up MFA if there has not been a successful step-up within the last 5 minutes And upon confirmation, the action executes within 5 seconds at p95 and returns success or failure in the Slack thread or via follow-up email And actions are idempotent and audit-logged with actor, target, reason, and correlation ID And partial failures roll back with an error message and a retry option And Snooze options (15m, 1h, 24h) suppress non-critical alerts for the same entity during the selected window And Mark as Expected suppresses alerts matching the same signature for 7 days unless severity is Critical
RBAC-Driven Action Visibility and Enforcement
Given a user receives an alert When the user lacks permissions for remediation Then one-click remediation actions are not shown; only a View Triage link is visible And if the user attempts to invoke an action via a crafted request, the server returns 403 and logs a security event And permitted roles are enforced as: Owner, Security Admin, and On-call may execute all actions; Project Admin may Snooze, Mark as Expected, and Open Incident; Viewer has no actions
Notification Preferences and Quiet Hours with Critical Override
Given a user has quiet hours configured and a non-critical alert occurs during that window When the alert is dispatched Then Slack DMs and emails to that user are suppressed And the alert is queued for delivery at the end of quiet hours if still relevant And critical alerts deliver to the current on-call user(s) within 10 seconds at p95 regardless of quiet hours And changes to notification preferences and quiet hours take effect within 5 minutes And the alert metadata indicates whether quiet hours or critical override were applied
Alert Rate Limiting, Deduplication, and Retry Policy
Given multiple anomalies fire rapidly for the same recipient When alerts are generated Then no more than 5 alerts per recipient are sent within any 5-minute window; additional alerts are summarized into a single digest message with a count And duplicate alerts for the same entity and policy within 10 minutes are deduplicated into a single thread update with an incremented counter And transient delivery failures trigger exponential backoff retries starting at 2s, doubling up to 60s max delay, with up to 5 attempts And HTTP 429 responses from Slack respect the Retry-After header And after exhausting retries, the alert is marked undeliverable, an internal ops alert is created, and details are recorded in telemetry
Delivery Telemetry, Tracking, and Correlation
Given any alert is processed When delivery attempts occur Then a correlation ID is attached end-to-end and included in logs, Slack metadata, and email headers (X-TimeTether-Correlation-ID) And telemetry records states: queued, sent, delivered, opened, clicked, action_executed with timestamps And email bounces and Slack API errors are captured with provider reason codes And the telemetry dashboard shows last-24h delivery success rate >= 99% And audit logs for actions are retained for 365 days and are exportable via CSV and API
Admin Quick-Fix Actions & Safe Rollback
"As an administrator, I want safe one-click fixes with rollback so that I can mitigate risk quickly without causing unintended disruption."
Description

Implements backend remediation endpoints for immediate actions such as OAuth token revocation, permission scope reduction, disabling cross-tenant sharing, temporary user or app blocks, and adjusting work-window settings. Performs pre-checks and impact analysis, requires confirmation, and supports automatic rollback within a defined window. Logs reasons, actors, and outcomes to the audit trail and integrates with Google/Microsoft Graph and Slack admin APIs for reliable execution.

Acceptance Criteria
Immediate OAuth Token Revocation from Alert Link
Given a Slack/Email anomaly alert includes a signed deep link for a specific OAuth token When an Admin with RBAC permission "remediate:tokens" opens the link Then the system verifies the signature, token TTL (<= 15 minutes), and actor identity and returns HTTP 200 And presents a pre-check summary with: provider (Google/MS/Slack), affected app/user, last use timestamp, and dependent features When the Admin confirms by typing "REVOKE" and submits Then the backend calls the provider API to revoke the token with an idempotency key and 3x exponential backoff on 5xx And marks the token as revoked in TimeTether within 5 seconds of provider success And pauses any jobs or webhooks that depend on the token within 10 seconds And writes an audit entry with actor, reason, token id, provider response, and correlation id And sends a success notification to the initiating channel/email within 30 seconds If provider revocation fails after retries Then the system returns HTTP 502 with a human-readable error and remediation tips and records a failed audit outcome
Permission Scope Reduction with Impact Analysis and Confirmation
Given a connected app has scopes [current_scopes] and an anomaly alert suggests over-scoped access When an Admin selects "Reduce to minimal scopes" for that app Then the system computes required minimal scopes based on enabled TimeTether features and org policy And displays an impact analysis listing features that will remain functional and those that would degrade if further reduced And blocks submission if requested scopes are below minimal with an inline error When the Admin confirms the change Then the system updates scopes via provider API, using idempotency and retry on transient errors And verifies post-change by fetching current scopes and comparing to target And updates configuration in TimeTether within 10 seconds And logs actor, old_scopes, new_scopes, impact summary, and provider call ids to the audit trail If verification fails (mismatch) Then the system rolls back to old_scopes automatically and marks outcome as "rolled_back" in the audit entry
Disable Cross-Tenant Calendar Sharing Policy Enforcement
Given org policy disallows cross-tenant calendar edits outside the allowlist When an Admin triggers "Disable cross-tenant sharing" from an alert Then the system performs pre-checks listing external domains currently interacting and count of active shares When confirmed Then the system applies policy updates via Microsoft Graph/Google Admin APIs to block new cross-tenant calendar writes except allowlisted domains And queues cancellation for pending invites that violate policy and prevents their dispatch And returns success only after provider acknowledges policy update or after two successful verification reads And records all changed policy objects with before/after snapshots in the audit trail If subsequent create/update attempts from TimeTether would violate the policy Then those attempts are rejected with HTTP 403 and a policy_violation code and are surfaced in the admin dashboard within 1 minute
Temporary User or App Block with Auto-Unblock and Safe Rollback
Given an anomaly identifies a suspicious user or app client_id When an Admin selects "Block for" and chooses a duration between 15 minutes and 72 hours Then the system validates duration against policy and shows services that will be impacted When confirmed Then the system blocks the principal via provider APIs (suspend user or disable app), confirms the block via read-back, and caches the block with an expiration timestamp And denies TimeTether actions from the principal with HTTP 423 (Locked) including remaining duration And creates a scheduled task to auto-unblock at expiration with jittered retries When the Admin chooses "Rollback" before expiration Then the system unblocks the principal, verifies access restored, links rollback to the original action in audit, and notifies the initiator If auto-unblock fails after retries Then the system escalates via Slack to on-call and marks the audit outcome as "rollback_failed" with error details
Adjust Work-Window Settings from After-Hours Access Alert
Given an after-hours access anomaly for a specific user/timezone When an Admin clicks "Adjust work window" from the alert Then the system displays the current work window, detected timezone, and recent after-hours events count When the Admin selects a new window within policy constraints and confirms Then the system updates the user’s work-window settings in TimeTether and synchronizes any affected scheduling rules And ensures the change takes effect for new scheduling computations within 2 minutes And posts a confirmation to the original alert thread with the new window and effective time If the timezone is ambiguous or stale (>7 days since last validation) Then the system prompts for timezone confirmation before applying changes
Safe Rollback Within Configured Window for Any Quick-Fix Action
Given a quick-fix action (token revoke, scope change, policy disable, block, work-window change) was executed And the configured rollback window is set (default 30 minutes, configurable per org) When an Admin requests rollback within the window Then the system runs pre-checks to detect conflicting changes since the action and displays a diff When confirmed Then the system reverses the original change with idempotency and verifies final state equals the pre-action snapshot And links the rollback to the original action via a correlation id in the audit trail If conflicts are detected (state drift) that prevent automatic rollback Then the system aborts rollback, returns HTTP 409 with a drift report, and provides guided manual steps
Audit Trail Completeness, Integrity, and Export
Given any quick-fix action or rollback is attempted Then the audit trail writes a single immutable entry per attempt containing: actor id, actor role, reason/risk reference, action type, target ids, pre-check summary, provider API call ids, timestamps (start/end), outcome (success/fail/rolled_back), and correlation id And audit entries are append-only and cannot be edited by any role And entries are queryable by time range, actor, outcome, and target id within 2 seconds for 95th percentile queries up to 10k rows And audit data is exportable as CSV/JSON via API with pagination and signed URLs And clock skew is bounded by NTP sync to <= 200 ms across services so timestamps are monotonic in a single action chain
Anomaly Triage Dashboard & Audit Trail
"As a compliance lead, I want a centralized triage view with a complete audit trail so that I can demonstrate controls and analyze patterns over time."
Description

Provides a centralized web console to review, filter, and triage anomalies by severity, policy, team, timezone, and service. Supports assignment, status changes, comments, tags, bulk actions, and evidence timelines with root-cause hints. Maintains a tamper-evident audit trail of alerts and administrative actions for compliance, with export (CSV/JSON), webhooks to SIEM/SOAR, and configurable data retention and access controls aligned with TimeTether RBAC.

Acceptance Criteria
Filter, Sort, and Persist Anomaly Views
Given a tenant has ≥10,000 anomalies across severities, policies, teams, timezones, and services When the user applies any combination of filters (severity, policy, team, timezone, service) with AND/OR logic and sets a sort order Then the list updates within 2 seconds (p95), returns only matching anomalies, displays total count, and persists filters/sort to the user profile for 7 days Given the user navigates pages or refreshes the browser When returning to the dashboard Then the previously applied filters and sort are restored and server-side pagination remains correct (no duplicates or gaps) Given no anomalies match the current filters When the query executes Then a zero-state is shown with a clear reset option and the query completes within 2 seconds (p95)
Assignment and Status Workflow Management
Given an anomaly is Unassigned When a user with Incident Manager or Admin role assigns it to a valid user and sets a status (New → In Progress → Resolved or New → Dismissed) Then the assignment, status transition, and optional due-by timestamp are saved within 1 second and visible without page reload Given an assignment is made When the assignee is set or changed Then the assignee receives a Slack and Email notification within 60 seconds (p95), and the previous assignee is notified of unassignment Given workflow rules When a user attempts an invalid transition (e.g., Resolved → New without reopen action) or lacks permission Then the action is blocked with a descriptive error and logged in the audit trail
Comments and Tags on Anomalies
Given a user with Analyst, Incident Manager, or Admin role When the user posts a comment with optional @mentions and attachments (≤10 MB each, up to 5 per comment) Then the comment appears with author, timestamp (local and UTC hover), and triggers notifications to mentioned users within 60 seconds (p95) Given an existing comment When the author or an Admin edits or deletes it Then the edit history is preserved, deletes are soft (content hidden, tombstone shown), and all actions are recorded in the audit trail Given tags exist When a user with Analyst+ role adds/removes tags Then up to 20 tags per anomaly are supported, tag suggestions appear from tenant taxonomy, and changes are immediately filterable
Bulk Actions on Selected Anomalies
Given the user selects 2–500 anomalies across pages When performing a bulk action (assign, status change, add/remove tags, dismiss with reason) Then a confirmation modal summarizes impact, the action executes atomically per item, partial failures are reported with reasons, and success/failure counts are shown Given bulk processing is underway When the operation completes Then 95% of jobs for 500 items finish within 60 seconds, with progress feedback, and rate limiting prevents API abuse (HTTP 429 with retry-after) Given a reversible bulk action (assign, tag) When the user clicks Undo within 5 minutes Then the system reverts the changes per item where still applicable and logs both actions
Evidence Timeline and Root-Cause Hints
Given an anomaly detail view with collected signals from multiple services When the user opens the Evidence tab Then events are ordered by event_time (UTC) then source, displayed in the user’s timezone with UTC on hover, and each entry shows source, actor, action, key fields, and deep links to the origin system Given attachments or artifacts exist When the user accesses them Then files are scanned, downloadable if allowed by policy, and sensitive fields are redacted per tenant redaction rules Given root-cause analysis is available When the timeline is rendered Then the top 3 root-cause hints with confidence scores (0–100%) are displayed, each citing the underlying evidence items
Tamper-Evident Audit Trail with Export and Webhooks
Given any alert or administrative action occurs When the audit record is written Then it includes id, tenant_id, utc_timestamp, actor, action_type, target_id, before/after (where applicable), ip, user_agent, hash, and prev_hash, and the append-only hash chain verifies successfully via /audit/verify Given a data integrity check runs When any record is altered or missing Then /audit/verify returns Fail with the first failing block id and the dashboard surfaces an integrity warning banner Given an admin requests an export When exporting CSV or NDJSON for a time range with optional filters (team, policy, action_type) Then the export streams within 30 seconds for up to 100,000 rows, uses documented schemas, proper CSV quoting/escaping, and NDJSON one-object-per-line format Given SIEM/SOAR webhooks are configured When a new audit event occurs Then a signed (HMAC-SHA256) POST is delivered within 120 seconds (p95), with 3 retries (1s, 5s, 25s) on 4xx/5xx, idempotency keys prevent duplicates, and a DLQ captures undeliverable events
RBAC Access Controls and Data Retention Policies
Given tenant RBAC is configured with roles (Viewer, Analyst, Incident Manager, Admin) When users access the dashboard Then permissions enforce least privilege: Viewer (read-only), Analyst (comment/tag), Incident Manager (assign/status/bulk), Admin (exports/webhooks/settings), with unauthorized actions returning 403 and being audited Given data retention is configured per tenant When retention periods elapse (e.g., anomalies 90 days, audit trail 365 days) and no legal hold exists Then a scheduled purge runs daily, irreversibly deleting eligible data and associated exports, while preserving hash continuity with tombstone markers Given a legal hold is placed on a scope (team/policy/time range) When retention would otherwise purge data Then affected records are retained until the hold is lifted, and UI/API reflect hold status
Noise Reduction: Baselines, Suppression, and Learning
"As a security analyst, I want the system to learn normal patterns and suppress noisy alerts so that I can focus on genuine risks."
Description

Builds behavior baselines per user/team/timezone for after-hours activity and cross-team interactions, and allows targeted suppressions (by user, app, time window, or policy). Incorporates feedback from marked false positives to adjust thresholds with decay, supports canary mode to measure precision/recall before rule changes go live, and surfaces metrics (alert volume, MTTA, false positive rate) to optimize policies. Ensures learned patterns never override explicit policy violations.

Acceptance Criteria
Baselines per User, Team, and Timezone
- Given 14 days of activity for a user in a defined timezone, When nightly baseline jobs run, Then per-user, per-team, and per-timezone thresholds for after-hours and cross-team interactions are generated and stored with a timestamp. - Given a user has <3 days of historical activity, When baselines are computed, Then global default thresholds are applied and the user baseline is marked as Cold Start. - Given a user changes their primary timezone, When the change is detected, Then a new timezone-specific baseline is created within 24 hours and the previous baseline is retained for 30 days for reference. - Given no qualifying activity for a user for 7 consecutive days, When baselines are recomputed, Then a decay factor is applied that lowers baseline confidence and is recorded in metadata. - Given a team membership change for a user, When synchronization occurs, Then the team-level baseline is recalculated within 24 hours and downstream alerts reference the updated team baseline.
Targeted Suppressions (User/App/Time Window/Policy)
- Given an admin creates a suppression scoped to {user=U, app=A, window=T1..T2}, When an alert matches scope during T1..T2, Then the alert is suppressed and the event is logged with suppression ID and reason. - Given a suppression with an expiration time T2, When T2 is reached, Then matching alerts resume without manual action and the suppression status changes to Expired in audit logs. - Given overlapping suppressions exist, When an alert matches multiple scopes, Then the alert is suppressed once and metrics count it once under Suppressed Alerts with each scope referenced. - Given suppression scope type = policy, When a violation occurs, Then it is suppressed only if the violated policy is explicitly included in the suppression scope. - Given any suppression is created or edited, When the change is saved, Then an immutable audit record captures actor, scope, justification, timestamps, and before/after values. - Given suppressions affect alerting, When the metrics dashboard is viewed, Then a Suppression Impact widget shows suppressed alert counts and top scopes for the selected period.
False Positive Feedback With Decayed Learning
- Given an alert is marked False Positive by an admin, When learning updates run, Then the corresponding signal sensitivity is reduced by at most 20% per day with exponential decay and a cumulative cap of 60%. - Given 14 days pass with no new False Positive feedback for a signal, When learning updates run, Then at least 50% of the prior sensitivity reduction is rolled back toward the original baseline. - Given an alert marked False Positive is reclassified as Valid within 7 days, When learning updates run, Then the associated adjustment is fully reversed within 1 hour and the reversal is audit logged. - Given ≥5 False Positive markings for the same signal/policy within 7 days, When the admin opens the alert policy view, Then the system surfaces a recommendation to create a targeted suppression with prefilled scope. - Given learning adjustments exist, When evaluating events, Then explicit policy thresholds are not relaxed by learning and any explicit violation still triggers an alert.
Canary Mode for Rule Changes
- Given a new baseline/threshold configuration, When canary mode is enabled at 10% traffic for 7 days, Then the new configuration runs in shadow and does not send live alerts. - Given at least 100 labeled alerts collected during canary, When analysis runs, Then precision, recall, and false positive rate are computed with 95% confidence intervals and displayed alongside control. - Given canary precision or recall degrades by >5% versus control, When the canary concludes, Then automatic promotion is blocked and a rollback option is provided. - Given canary metrics meet or exceed guardrails, When the admin chooses Promote, Then the new configuration becomes active within 15 minutes and a change record is created. - Given a canary is active, When any user views the policy page, Then a banner indicates canary status, start/end dates, and current sample size.
Noise Metrics and Operational Reporting
- Given the metrics dashboard is opened, When a 30-day range is selected, Then alert volume, MTTA, false positive rate, and suppression counts render within 2 seconds for tenants up to 10k users. - Given filters (team, timezone, policy) are applied, When charts and tables refresh, Then all widgets reflect the filters consistently and totals reconcile within ±1%. - Given an alert is acknowledged via Slack or Email, When MTTA is computed, Then the acknowledgment time is reflected in MTTA within 60 seconds. - Given Export CSV is requested for the current view, When processing completes, Then a downloadable CSV with applied filters is available within 1 minute and includes a data dictionary header. - Given daily operation, When the date rolls over UTC, Then metrics for the prior day are finalized and no longer change except for late-arriving events flagged as corrections.
Explicit Policy Violations Always Alert
- Given an event matches an explicit policy violation, When the learned baseline would otherwise suppress or dampen the alert, Then the alert is still generated and delivered to configured channels within 30 seconds. - Given a suppression exists scoped to user/app only, When a policy violation occurs outside the suppression's policy scope, Then the alert is not suppressed and includes a Policy Precedence indicator. - Given evaluation of events, When both learned thresholds and explicit rules apply, Then explicit rules are evaluated last and their decisions override learning, with the evaluation path recorded in the alert details. - Given the regression test suite runs in CI, When policy precedence tests execute, Then all tests validating policy-over-learning precedence pass before deployment can proceed.

Auto-Revoke Sweep

Nightly sweep identifies stale, unused, or orphaned calendar connections and revokes them automatically with owner notification. Keeps permission surface minimal without manual housekeeping.

Requirements

Configurable Sweep Orchestrator
"As a TimeTether platform admin, I want a reliable, configurable nightly sweep so that stale calendar connections are processed at scale without disrupting provider APIs or tenant operations."
Description

Implements a nightly, configurable batch process that scans all tenant calendar connections in controlled waves. Supports cron-based scheduling, per-tenant and per-provider partitioning, concurrency controls, API rate limiting, exponential backoff, and idempotent execution. Includes dry-run mode to preview impact without revoking, failure isolation to prevent one tenant from blocking others, and observability (structured logs and metrics) to track throughput, errors, and duration.

Acceptance Criteria
Nightly Cron Trigger Executes Waves
Given a cron schedule "0 2 * * *" and configuration waveSize=500 and waveInterval=60s When the system time reaches 02:00:00 UTC Then the orchestrator starts wave 1 within 60 seconds And schedules subsequent waves every 60 seconds until all eligible connections are assigned And assigns no more than 500 connections per wave And generates a unique sweep_id included in all logs and metrics for the run
Per-Tenant and Per-Provider Partitioning
Given multiple tenants (e.g., T1, T2) and providers (e.g., Google, Microsoft) And partitioning mode is tenant+provider When the orchestrator builds execution batches Then connections are grouped strictly by tenant_id and provider And no batch contains connections from multiple tenants And metrics emit counts per {tenant_id, provider} And each partition runs in an isolated execution unit/queue
Concurrency Controls and Provider Rate Limiting
Given maxConcurrentPartitions=10 and provider limits: Google qps=50, Microsoft rpm=600 When the sweep runs under load Then the orchestrator never executes more than 10 partitions concurrently And per-provider request rates do not exceed configured limits over any rolling 60-second window And excess work is queued without data loss or errors due to throttling
Exponential Backoff and Retry Policy
Given a transient provider error (HTTP 429 or 5xx) When a request fails Then the orchestrator retries with exponential backoff starting at 1s, multiplier 2.0, max delay 60s, jitter ±20%, up to 5 attempts And non-retryable 4xx errors are not retried And each attempt is logged with attempt number and final outcome And metrics record retries and final statuses by {tenant_id, provider}
Idempotent Execution Across Retries
Given a sweep run with sweep_id=S processing connection=C When the orchestrator is re-run for the same sweep_id due to retry or crash recovery Then no duplicate revocations or duplicate owner notifications occur for C And the final state of C matches that of a single successful run And idempotency keys prevent re-execution of side effects and are recorded in logs
Dry-Run Mode Produces No Side Effects
Given dryRun=true for sweep_id=D When processing connections eligible for revocation Then no revoke API calls are executed And a would_revoke event is logged per candidate with connection_id and reason And metrics include would_revoke counts per {tenant_id, provider} And no owner notifications are sent
Failure Isolation and Observability Coverage
Given tenant T1 experiences repeated unrecoverable errors during its partition When the sweep continues processing other partitions Then failures in T1 do not block or slow processing for T2 and T3 And the orchestrator emits metrics: sweep_throughput, sweep_errors, sweep_duration, partition_errors labeled with sweep_id, tenant_id, provider And structured logs for each action include: timestamp, sweep_id, tenant_id, provider, connection_id, action, attempt, outcome, error_code
Connection Staleness Heuristics
"As a security-conscious admin, I want accurate, explainable staleness detection so that we revoke only truly unused or orphaned connections and avoid disrupting active users."
Description

Defines policy-driven rules and a scoring model to identify stale, unused, or orphaned calendar connections across Google Workspace and Microsoft 365. Signals include last successful sync, last invite sent, token age/expiry, user deactivation, app uninstall, missing webhooks, no meeting activity over N days, and ownership mismatches. Thresholds are configurable per tenant with reason codes attached to each decision, plus safeguards that skip revocation when upcoming meetings exist within a grace window.

Acceptance Criteria
Tenant-Configurable Scoring and Threshold Decision Logging
Given a tenant-defined set of signal weights and a staleness threshold T When the nightly sweep evaluates a connection Then the system computes a staleness score (0–100) using the configured weights And compares the score to T to decide revoke or retain And persists tenant_id, connection_id, score, decision, evaluated signals, and top_3 reason_codes with timestamps to the audit log
Automatic Revocation and Owner Notification
Given a connection’s score >= the tenant threshold and it is not exempt and not within a grace window When the nightly sweep executes Then the system revokes provider tokens/permissions for that connection and sets status "revoked_by_policy" And records a revocation_id, provider response status, and completion time in the audit log And sends an email and in-app notification to the connection owner within 15 minutes listing the connection, reason_codes, timestamp, and a one-click re-auth link
Grace Window for Upcoming Meetings
Given a connection has upcoming meetings within the tenant-configured grace window G days When its staleness score exceeds the tenant threshold Then the system does not revoke the connection And sets state to "defer_revocation" with next_review = (day after last upcoming meeting) + 1 day And notifies the owner of the deferment including reason_codes and next_review date
Immediate Revocation for Orphaned or Deactivated Accounts
Given a connection’s user is deactivated in the IdP or provider, or the app is uninstalled, or an ownership mismatch is detected When the condition is detected via event or during the sweep Then the system revokes the connection immediately regardless of staleness score And routes notification to the tenant admin if the owner mailbox is unavailable And logs reason_code in {user_deactivated, app_uninstalled, ownership_mismatch} and the detection source
Missing Webhooks and No Activity Over N Days
Given a connection has missing or failing webhooks for more than W hours and no successful sync and no meeting activity for at least N days (tenant-configured) When the sweep runs Then the connection is marked stale with reason_codes including webhook_missing and inactivity_N_days And it becomes eligible for revocation subject to grace window and exemptions And the system attempts one auto-repair of webhooks before revocation and logs the attempt outcome
Exemptions and Allowlist Handling
Given a connection is allowlisted or tagged as service/system account by tenant policy When the sweep evaluates it Then the system computes a staleness score but never revokes it And records decision "retain_exempt" with reason_code exempt_policy And includes the connection only under an "exempt-retained" section in any notification digest
Provider Coverage and Safe Retry on Errors
Given revocation requests are issued to Google Workspace and Microsoft 365 provider APIs When a revocation call fails transiently (5xx/429) Then the system retries up to R times with exponential backoff and jitter and marks the connection "revocation_pending" until success And when a permanent failure occurs (non-retryable 4xx), the decision is recorded as "retain_error" with provider_error_code and the tenant admin is alerted And the local connection status remains consistent with provider state; no "revoked_by_policy" is recorded until provider confirmation
Automated Revocation & Token Cleanup
"As a platform engineer, I want automatic and complete revocation of stale connections so that our permission surface is minimized and no residual credentials remain usable."
Description

Executes provider-specific revocation for identified connections and fully removes local credentials and webhooks. Calls Google/Microsoft revoke endpoints, purges refresh tokens from secure storage, disables related subscriptions, and updates connection state atomically. Retries transient failures with jittered backoff and records reason codes. Ensures downstream services receive state-change events to prevent further use of revoked credentials.

Acceptance Criteria
Nightly Sweep: Google Connection Revocation and Cleanup
- Given a stale Google Calendar connection with an active refresh token and at least one active watch channel - When the nightly sweep executes the revocation task for that connection - Then the system calls Google's token revocation endpoint and receives a 200–204 response within 5 seconds - And the connection's refresh token and access token are purged from secure storage immediately after successful revocation - And all Google watch channels for that connection are stopped and deleted; provider responses are 200–204 - And the connection state transitions to "revoked" with reason_code set to the detection reason (e.g., STALE_UNUSED or ORPHANED) and revoked_at timestamp recorded
Nightly Sweep: Microsoft Connection Revocation and Cleanup
- Given a stale Microsoft 365 calendar connection with an active refresh token and active Graph subscription(s) - When the nightly sweep executes the revocation task for that connection - Then the system calls the Microsoft identity/Graph revocation endpoint(s) and receives a 2xx response within 5 seconds - And the connection's refresh token and access token are purged from secure storage immediately after successful revocation - And all Graph subscriptions for that connection are deleted/expired via API (2xx) and removed locally - And the connection state transitions to "revoked" with reason_code set to the detection reason and revoked_at timestamp recorded
Atomic State Update and Idempotent Cleanup
- Given a connection targeted for revocation with pending steps (provider revoke, token purge, webhook disable, state update) - When any step fails or times out - Then no partial state is committed; the connection state remains unchanged and credentials/webhooks remain as before; the failure is logged with reason_code and correlation_id - And a retry is scheduled according to the transient/permanent error policy - Given the revocation task is executed multiple times for the same already-revoked connection - When the task runs - Then the operation is idempotent: no provider calls are made, no credentials exist in storage, no webhooks exist, and the state remains "revoked"
Transient Failures: Jittered Backoff and Reason Codes
- Given the provider revoke API returns a transient error (HTTP 5xx) or a network timeout - When the revocation task runs - Then the system retries at least 3 times with exponential backoff and 10%–30% random jitter between attempts, not exceeding a 5-minute max delay - And each failed attempt records attempt_number, error_class, and last_error_at; reason_code is set to REVOKE_RETRY_IN_FLIGHT until success - And upon final success, the connection is marked "revoked" and the final reason_code reflects the original detection reason - And given the provider returns a permanent error indicating already invalidated credentials (e.g., HTTP 400 invalid_grant) - When the task runs - Then the system treats it as successful revocation: tokens are purged, webhooks disabled, and state set to "revoked" with the original detection reason_code
State-Change Event Emission and Downstream Enforcement
- Given a connection has been successfully revoked - When the state transitions to "revoked" - Then an event "connection.revoked" is published within 2 seconds to the event bus with fields: connection_id, provider, previous_state, new_state=revoked, reason_code, revoked_at, correlation_id, version - And the event is delivered at-least-once; on no ack within 10 seconds, it is retried with backoff until a 24-hour TTL is reached - And any queued or scheduled jobs referencing the connection are cancelled within 60 seconds - And downstream API calls attempting to use the connection after revocation receive a 403/410 and perform no provider requests
Webhook/Subscription Disablement and Callback Handling
- Given active provider webhooks/subscriptions exist for the connection - When the revocation task completes - Then unsubscribe/delete calls are made for each webhook/subscription; provider responses are 2xx - And local webhook/subscription records are disabled or removed, and no new deliveries are processed for the connection after revoked_at - And any incoming callbacks referencing revoked webhook IDs are rejected with HTTP 410, logged with connection_id and reason_code, and do not trigger downstream processing
Owner Notification & One-Click Reconnect
"As a connection owner, I want a clear notification with a one-click path to restore access so that I can quickly recover if a necessary connection was revoked."
Description

Notifies connection owners upon revocation via email and Slack with a detailed reason and a one-click reconnect link to reauthorize. Supports localization, digesting multiple revocations into a single message, rate limiting to prevent notification spam, and secure deep links that expire. Includes fallback instructions for manual reconnect and tracks reconnect outcomes for efficacy reporting.

Acceptance Criteria
Email Notification with Reason and One-Click Reconnect
Given a calendar connection is auto-revoked by the nightly sweep and the owner has a valid email on file When the revocation is persisted Then an email notification is sent within 5 minutes of revocation And the subject includes "TimeTether" and "Access revoked" And the body includes the connection name/ID, provider, reason code, human-readable reason, and revocation timestamp in the owner’s locale And the body includes a one-click reconnect button/link And the message is localized to the owner’s preferred locale with English fallback And the notification send outcome and provider message ID are recorded
Slack Notification with Reason and One-Click Reconnect
Given a calendar connection is auto-revoked by the nightly sweep and the owner has an active Slack user binding When the revocation is persisted Then a Slack DM is sent within 5 minutes of revocation And the message includes the connection name/ID, provider, reason code, human-readable reason, and revocation timestamp in the owner’s locale And the message contains a one-click reconnect button/link And the message is localized to the owner’s preferred locale with English fallback And the Slack message timestamp and channel/user IDs are recorded
Digest Multiple Revocations per Owner per Channel
Given multiple connections for the same owner are revoked within the configured digest window When the digest job runs for that window Then a single aggregated notification per channel (email, Slack) is sent to the owner And the digest lists each revoked connection with provider, reason, and revocation timestamp And individual notifications for those revocations are not sent in addition to the digest And if the digest exceeds N items (configurable), the remainder are summarized with a count and a link to view details And the digest includes one-click reconnect links for each listed connection
Notification Rate Limiting per Owner
Given per-owner, per-channel rate limits are configured (e.g., X/hour and Y/24h) When revocations would trigger notifications beyond the configured limits Then additional notifications are suppressed for that window And suppressed events are included in the next eligible digest for that owner And suppression decisions and counts are logged with timestamps And no more than X notifications are sent in any rolling 1-hour window and no more than Y in any rolling 24-hour window per channel
Secure Expiring One-Click Reconnect Link
Given a one-click reconnect link is generated for a revoked connection When the link is created Then it uses a signed, single-use token scoped to the owner and the specific connection And it expires after the configured duration (e.g., 24 hours) And it is only served over HTTPS and cannot be used from a different owner/account When the link is used before expiry Then the connection is reauthorized without requiring additional input beyond necessary provider consent And the token is invalidated immediately after use When the link is used after expiry or reuse is attempted Then the reconnect is blocked, a safe error is shown, and no changes are made to permissions
Fallback Manual Reconnect Instructions
Given a notification is delivered (email or Slack) When the one-click reconnect link is unavailable, expired, or blocked Then the message includes clear, step-by-step manual reconnect instructions with a hyperlink to the account settings page And the instructions are localized to the owner’s preferred locale with English fallback And following the instructions enables the owner to successfully reauthorize the same provider and account And a manual reconnect completion event is recorded with correlation to the originating revocation
Reconnect Outcome Tracking and Efficacy Reporting
Given notifications (email and/or Slack) are sent for revocations When the recipient interacts or reconnects Then events are recorded for notification_sent, delivery (if available), opened (if tracked), link_clicked, reconnect_started, reconnect_completed, reconnect_failed And each event includes owner ID, connection ID, channel, reason code, message ID, and timestamps And events are joinable via a correlation ID to attribute reconnects to specific notifications And aggregate metrics (send volume, CTR, reconnect rate, time-to-reconnect) are queryable by channel, locale, and reason And no PII beyond necessary identifiers is stored, and data retention follows the configured policy
Exemptions and Grace Periods
"As an admin, I want flexible exemptions and grace periods so that critical connections are protected while we still reduce overall permission exposure."
Description

Provides policy controls to whitelist specific users, service accounts, domains, or connection types from automatic revocation and to set grace periods before action. Supports time-bound exemptions with auto-expiry, just-in-time exemptions initiated from notifications, and conditional rules (e.g., skip if user is on leave or if team is in critical release window). Exemptions are evaluated during the sweep and recorded with rationale.

Acceptance Criteria
Whitelist by User and Service Account
Given a nightly sweep and a policy that whitelists specific user IDs and service accounts When the sweep evaluates connections owned by those identities Then those connections are not marked for revocation And the evaluation log records decision="skipped", reason="whitelist", and the matched rule identifier And when an identity is removed from the whitelist before the next sweep Then the next sweep evaluates their connections under normal revocation rules
Domain and Connection-Type Exemptions
Given a policy with domain exemptions (including exact and subdomain wildcard patterns) and specified exempt connection types When the sweep evaluates a connection whose owner email domain matches an exempt domain or whose connection type is exempt Then the connection is skipped from revocation And wildcard matching behaves as: foo.corp.example matches *.corp.example; corp.example matches exact if listed; bar.example does not match And the evaluation log records decision="skipped", reason="domain_or_type_exempt", and the matched rule identifier
Configurable Grace Periods Before Revocation
Given a policy that defines a default grace period (e.g., 72 hours) and optional overrides by connection type When a connection first qualifies for revocation at time t0 (UTC) Then a revocation_at timestamp is set to t0 plus the applicable grace period And no revocation occurs before revocation_at And a single impending-revocation notification is sent at t0 including the remaining time And if the connection shows qualifying activity before revocation_at, it exits the revocation pipeline and the scheduled revocation is cleared And if revocation_at has passed, the next sweep revokes the connection and records the exact effective time
Time-Bound Exemptions with Auto-Expiry
Given a time-bound exemption with start_at and end_at (UTC) for a specific connection, user, or scope When the sweep runs between start_at and end_at (inclusive) Then qualified revocations for the covered resources are skipped And when the sweep runs after end_at Then the exemption no longer applies and normal rules evaluate And the evaluation log includes exemption_id, active_window, and expiry outcome And if configured, a pre-expiry notification is sent N hours before end_at
Just-in-Time Exemptions Initiated from Notifications
Given an impending-revocation notification includes a signed JIT exemption link valid for 24 hours When the recipient opens the link, selects an allowed duration (up to the policy max), and submits a rationale Then a time-bound exemption is created immediately for the targeted connection or scope And the connection is removed from the current revocation queue and honored by the next sweep And attempts with expired or tampered links are rejected with 401 and no exemption is created And the action is audited with actor, reason, duration, and timestamp
Conditional Rules: On Leave or Critical Release Window
Given integration with approved PTO sources and a team release calendar that designates critical windows When a connection qualifies for revocation but the owner is on approved leave overlapping the sweep date Then the connection is skipped with reason="on_leave" And when the owner's team is within an active critical release window Then the connection is skipped with reason="critical_release_window" for the duration of the window And when neither condition applies, normal evaluation proceeds
Evaluation Recorded with Rationale and Traceability
Given any sweep evaluation When a connection is processed Then an evaluation record is written containing: connection_id, owner_id, decision (revoked|skipped|exempt), matched_rule_id(s), rationale text, evaluated_at (UTC), effective_at (UTC), actor (system|user), and grace_period_remaining (if applicable) And records are queryable by connection_id, owner_id, and date range via the admin API And records are immutable and retained for at least 365 days
Audit Logging and Compliance Evidence
"As a compliance officer, I want complete audit evidence of revocations and notifications so that we can demonstrate least-privilege enforcement and respond to audits efficiently."
Description

Creates an immutable audit trail of detection decisions, revocation actions, notifications sent, and reconnect outcomes. Each record includes who/what/when/why, provider identifiers, reason codes, and policy version. Supports export APIs and scheduled reports for SOC 2/GDPR evidence, with retention policies and data minimization to avoid storing unnecessary personal data.

Acceptance Criteria
Immutable Audit Log for Revocation Sweep Events
Given the nightly Auto-Revoke sweep runs When a detection decision or revocation action occurs Then an audit record is appended and no existing records are modified or deleted And any API attempt to update or delete an audit record returns 405 Method Not Allowed and logs a tamper_attempt event And each record includes content_hash and previous_hash enabling chain verification And calling GET /audit-logs/integrity-check with the time window returns status=passed for unaltered chains
Complete Metadata Capture per Audit Event
Given an audit event of type in {detection, revocation, notification_sent, reconnect_outcome} When the event is written Then the record contains non-null fields: event_id, tenant_id, actor_type, actor_id, event_type, occurred_at (UTC ISO-8601 with ms), provider, provider_account_id, resource_id, reason_code, policy_version, correlation_id, request_id And reason_code is one of {STALE_CONNECTION, ORPHANED_ACCOUNT, UNUSED_SCOPE, OWNER_REQUEST} And actor_type is system for automated sweeps or user for manual overrides And occurred_at is within 2 seconds of the action completion time And for event_type=notification_sent, fields channel (email|slack|teams), destination_id, template_id, delivery_status (queued|sent|bounced|failed), and provider_message_id (nullable until known) are present And for event_type=reconnect_outcome, fields outcome (success|failure) and failure_code (nullable) are present
Export API for Compliance Evidence
Given a user with role ComplianceAdmin When they request GET /exports/audit-logs?from=...&to=...&event_type=...&provider=...&actor_id=...&correlation_id=...&format=csv Then the response status is 200 and returns only the requesting tenant’s data with pagination (cursor-based) and total_count And the CSV conforms to RFC4180, includes a header row and dictionary_version, and supports gzip via Accept-Encoding gAnd the payload includes file_sha256 and signed_at with a detached signature for integrity verification And a 10,000-record export completes within 30 seconds under nominal load And users without role ComplianceAdmin receive 403 Forbidden
Scheduled Compliance Reports Delivery
Given a tenant configures a weekly SOC2/GDPR evidence report to an S3 destination When the schedule elapses Then a report for the configured time window is generated and delivered with server-side encryption (SSE-S3 or SSE-KMS) And delivery failures are retried with exponential backoff for up to 24 hours, after which report_delivery_failed is logged and tenant admins are alerted And each run logs report_generated with report_id, time_window, record_count, and delivery_status And schedules can be set in tenant local timezone and are executed correctly across DST transitions
Retention and Legal Hold Enforcement
Given retention policies are configured: default=400 days; notification_metadata=90 days; integrity_hashes=400 days When records exceed their retention period Then they are purged within 24 hours and a records_purged audit event is appended with counts per event_type And records under an active legal_hold (tenant- or correlation-level) are excluded from purge until the hold is cleared And purged records are irrecoverable via product APIs and excluded from exports And changes to retention policy apply prospectively and are logged as retention_policy_updated with old/new values and approver_id
Data Minimization and PII Redaction
Given an audit event is stored Then no message bodies, OAuth tokens, or calendar event descriptions are stored; only provider identifiers and metadata And user emails are stored as email_hash (SHA-256 salted per-tenant) and masked in exports unless include_pii=true And when include_pii=true is used by a ComplianceAdmin, a justification string is required and logged, and unmasked emails appear only for the requested subjects And each field has a data_classification, and exports exclude fields labeled restricted unless include_restricted=true is provided by a ComplianceAdmin
End-to-End Traceability From Detection to Reconnect
Given a connection is detected as stale and later revoked and the owner reconnects When querying GET /audit-logs/trace?correlation_id={id} Then the response returns the ordered sequence [detection, revocation, notification_sent, reconnect_outcome] sharing the same correlation_id And time_deltas between consecutive events are included And if reconnect has not occurred within 30 days, the trace includes status=reconnect_pending and last_notification_sent timestamp
Admin Reporting and Alerts
"As a TimeTether admin, I want reporting and alerts on the sweep outcomes so that I can monitor effectiveness and act quickly on anomalies."
Description

Delivers a dashboard and alerting that summarize revocation trends, coverage, reasons, false-positive rates, pending grace-period items, and reconnect success. Provides daily/weekly summaries to email/Slack, drill-down to affected users, CSV export, and thresholds that trigger alerts when anomalies occur (e.g., spike in orphaned tokens or provider errors).

Acceptance Criteria
Dashboard: Revocation, Coverage, and Reconnect Trends
Given an admin with org-level reporting permissions and at least one sweep execution in the selected period (7, 30, or 90 days) When the admin opens the Admin Reporting dashboard and selects a time range Then the dashboard displays: total revocations (count), revocation rate (per 100 connected accounts), coverage (% of active users with valid connections), revocations by reason (count and %), revocations by provider (count and %), reconnect success rate (7-day and 30-day %), and daily trend charts for each metric And all timestamps and aggregations use the org default timezone And metrics reflect data updated no more than 15 minutes after the most recent sweep completion And empty states render “No data for selected range” with zeroed metrics when applicable And switching time ranges updates all widgets within 2 seconds for cached ranges and 10 seconds for uncached ranges
Alerts: Threshold-Based Anomaly Notifications
Given anomaly thresholds are configured for orphaned token count, provider error rate, and spike detection (> 3σ above 30-day mean) When a nightly sweep completes and any metric breaches its configured threshold Then an alert is sent to the configured Slack channels and email recipients within 5 minutes of sweep completion And the alert payload includes: metric name, observed value, threshold, baseline window, impacted providers, sample size, and a link to the relevant drill-down view And duplicate alerts for the same metric and day are suppressed for 60 minutes unless the observed value increases by ≥ 10% And alert severity is assigned per metric (e.g., High for provider errors, Medium for orphaned spikes) and appears in the message
Drill-Down and CSV Export: Affected Users Details
Given an admin clicks a dashboard segment or an alert deep link When the detail view loads Then it lists affected identities with columns: User Name, Email, User ID, Provider, Connection ID, Status (Revoked/Pending/Failed), Reason, Last Token Activity, Sweep Run ID, Action Timestamp And the list supports filtering by Provider, Reason, Status, Date Range and searching by Email/User ID And the list supports sorting by any column in ascending/descending order And clicking Export CSV downloads the currently filtered dataset with identical columns in UTF-8 CSV format with header row, comma delimiter, double-quote escaping, and LF line endings; timestamps are ISO 8601 with timezone offset And exports up to 100,000 rows complete within 60 seconds and are accessible via a pre-signed link that expires after 24 hours And access to drill-down and exports is enforced by org RBAC permissions
Quality Metrics: False-Positive Rate Tracking
Given revocations can be misclassified When an admin marks a revoked item as “False Positive” within 14 days of revocation Then the item’s status changes to “Reverted – False Positive” and the connection is added to a 14-day exclusion list to prevent immediate re-revocation And the dashboard false-positive rate for a selected period is calculated as (False Positives within the period) / (Revocations within the period), displayed to two decimal places And the false-positive rate, counts, and trend update within 15 minutes of the change And all mark/unmark actions are audit logged with actor, timestamp, and reason
Grace Period: Pending Items Visibility
Given the system flags connections for revocation with a configurable grace period When pending grace-period items exist in the selected range Then the dashboard displays counts and daily trends of pending items by Provider and Reason, plus average time remaining And a drill-down lists each pending item with User, Email, Provider, Reason, Detected At, Grace Ends At, and Current Owner Notification Status And when a pending item expires and is revoked, it automatically moves from Pending to Revoked metrics within 15 minutes
Summaries: Daily and Weekly Email/Slack Digests
Given summary schedules and recipients are configured for the org When the daily and weekly digests run at the configured times in the org timezone Then recipients receive summaries via email and Slack containing: revocations (count), revocation rate, top reasons (top 3), provider breakdown, false-positive rate, pending grace items (count), reconnect success rate (7-day), and any breached thresholds during the period And each summary includes deep links to the dashboard and the latest drill-down And delivery success/failures are logged and surfaced in an Admin > Notifications log And recipients can be added/removed without requiring a deployment, and changes take effect by the next scheduled digest
Alert Threshold Configuration and Preview
Given an admin opens Alerts Settings When they add or edit a threshold for a metric (e.g., orphaned token count, provider error rate) Then they can set operator (>, ≥), value, baseline window, sensitivity (σ), severity, and delivery channels (Slack/Email) And saving creates a new versioned configuration with author, timestamp, and change summary And a “Preview last 30 days” function renders hypothetical breaches under the new configuration within 5 seconds And reverting to a prior version restores its behavior immediately and is audit logged

One-Click Import

Connect Google Workspace or Microsoft 365 once and auto-import the last 90 days of team calendars, participants, and preferences. Directory-aware matching and de-dupe get your whole team recognized instantly, shrinking setup from hours to seconds.

Requirements

OAuth Workspace Connectors
"As an IT admin, I want to connect our Google Workspace or Microsoft 365 tenant in one click so that TimeTether can securely access calendars and directories without manual setup."
Description

Implement secure, one-click OAuth 2.0 connection flows for Google Workspace and Microsoft 365 with least-privilege scopes, tenant-wide admin consent, and domain scoping. Persist tokens in a hardened secrets store with rotation, refresh, and revocation handling. Support multi-tenant organizations, SSO-enforced admins, and clear permission rationale so TimeTether can initiate calendar and directory imports without manual configuration.

Acceptance Criteria
Google Workspace OAuth Admin Consent Flow
Given a Google Workspace Super Admin from a verified company domain has not connected Google, When they click "Connect Google Workspace", Then the OAuth request includes only these scopes: calendar.readonly, admin.directory.user.readonly, admin.directory.resource.calendar.readonly; And access_type=offline is requested; And prompt=consent is present for first-time consent. Given the admin grants consent, When the authorization code is exchanged, Then the system persists access and refresh tokens with expiry metadata; And sets provider status to "Connected (Google)" within 15 seconds of callback. Given the connection is complete, When the system calls CalendarList.list and Directory Users.list using the stored tokens, Then both calls return HTTP 200 and non-empty results for at least one user in the domain.
Microsoft 365 OAuth Admin Consent & Tenant Scoping
Given an Azure AD Global Admin from the target tenant initiates connection, When they click "Connect Microsoft 365", Then the OAuth authorize URL includes the tenant parameter equal to the admin's tenant_id and requests only these delegated scopes: Calendars.Read, User.Read.All, Directory.Read.All; And the consent screen indicates admin consent is required. Given consent is granted, When the authorization code is exchanged, Then the system persists tokens and the resolved tenant_id; And sets provider status to "Connected (M365)" within 15 seconds of callback. Given the connection is complete, When Graph API requests are made, Then all requests include the stored tenant_id context and return HTTP 200 for /users and /me/events; And requests from users in other tenants are blocked or return HTTP 403.
Secrets Storage, Rotation, Refresh, and Revocation Handling
Given any provider connection is established, Then tokens are stored in a hardened secrets store encrypted with a KMS-managed key (AES-256 or equivalent) and access is restricted to the scheduler service identity; And all token access is audit-logged with timestamp, actor, and purpose. Given an access token is near expiry (<5 minutes), When the scheduler requests a token, Then the system refreshes it using the refresh token transparently and updates expiry metadata; And retries at most 1 time on transient errors with exponential backoff. Given three consecutive refresh failures within 15 minutes or an invalid_grant/invalid_client error, Then the connection status changes to "Disconnected – Action Required" within 10 minutes; And an admin alert is sent; And further import jobs are halted. Given a KMS key rotation event occurs, Then all stored tokens remain decryptable and new writes use the new key within 1 hour.
Multi-Tenant & Domain Scoping Isolation
Given multiple Google domains and/or Microsoft tenants are connected, When any import or API call is executed, Then the query is scoped by the specific connection's domain/tenant and organization_id; And cross-tenant data access is prevented and returns HTTP 403. Given a user outside connected domains attempts to connect, Then the system blocks the attempt and displays "Domain not allowed" with the list of allowed domains. Given two tenants with users sharing the same email local-part, When directory data is processed, Then records are isolated by domain/tenant and no collisions occur in storage (composite key includes tenant_id/domain).
SSO-Enforced Admin Authorization & Permission Rationale
Given the organization enforces SSO for admins, When a non-admin or non-SSO-authenticated user visits the connectors page, Then the Connect buttons are disabled and a tooltip reads "Admin access required via SSO". Given an SSO-authenticated org admin initiates a connection, Then a pre-consent screen displays each requested scope with a plain-language rationale and links to the provider's docs; And the admin must acknowledge via checkbox before proceeding. Given the admin declines the acknowledgment or closes the dialog, Then no external OAuth request is initiated and no partial state is persisted.
One-Click Import Kickoff After Connection
Given at least one provider connection is "Connected", When the admin clicks "Start Import", Then the import job is enqueued within 5 seconds and appears in Activity Log with provider, tenant/domain, and scope summary. Given the job is enqueued, When it runs, Then it can read calendars and directory using the stored tokens without additional configuration; And completes an initial connectivity check within 60 seconds returning "Ready to import". Given the connection is not "Connected", When "Start Import" is clicked, Then the system prevents execution and displays "Connect a workspace first".
90-Day Calendar History Ingestion
"As a team lead, I want TimeTether to ingest the last 90 days of my team’s calendars so that it can understand patterns and current commitments immediately."
Description

Automatically ingest the past 90 days of events for all discovered team calendars, including recurring series, attendee lists, organizer, response states, and timezone data. Respect privacy settings by defaulting private events to busy/free only, map provider fields to TimeTether’s event model, and ensure idempotent imports. Use paginated batch pulls with checkpoints to support large orgs and enable immediate scheduling insights upon completion.

Acceptance Criteria
Exactly 90-Day Window Import per Calendar
Given an organization connects Google Workspace or Microsoft 365 at timestamp T0 And team calendars are discovered for all members When ingestion starts Then only events with start or occurrence dates between T0-90 days (inclusive) and T0 (inclusive) are imported And events older than T0-90 days are not imported And recurring series are imported only for occurrences within the window And imported events preserve original timezone and are normalized to UTC in the model
Field Mapping Completeness and Fidelity
Given provider events from Google Workspace and Microsoft 365 including single events, recurring series, exceptions, and cancellations When ingestion completes Then TimeTether stores for each event: provider eventId, seriesId (if applicable), organizer, attendee list with emails, per-attendee response status, privacy flag, start/end in original timezone and UTC, recurrence rules, exceptions, and cancellation state And provider-specific fields are normalized to the canonical model without loss of required information And nullable/missing fields are handled per mapping spec without ingestion errors
Privacy Respect: Private Events as Free/Busy Only
Given a private event exists in a user’s provider calendar When the event is ingested Then TimeTether stores only availability state (busy/free), start/end time, and timezone metadata And TimeTether does not store title, description, location, conferencing links, or attendee emails for private events And downstream insights consume only availability from such events And audit logs record that a privacy-restricted event was processed without sensitive fields
Idempotent Re-Imports and Change Detection
Given a full ingestion for the last 90 days completed at time T1 And the same ingestion job is re-run at time T2 without provider-side changes When the re-import executes Then no duplicate events are created and no net changes are applied When provider-side changes occur (new, updated, deleted events) Then TimeTether inserts new events, upserts updates, and marks deletions as removed And event counts and checksums reflect only the delta between T1 and T2 And operations are traceable via idempotency keys derived from provider IDs and lastModified timestamps
Paginated Batch Pulls with Checkpoints and Resume
Given an organization with ≥1,000 calendars and ≥500,000 events within the 90-day window When ingestion runs using provider pagination Then requests are batched within provider page-size and rate-limit constraints And per-calendar, per-page checkpoints are persisted after each successful page And on transient failure the job resumes from the last checkpoint without reprocessing completed pages And exponential backoff and jitter are applied on HTTP 429/5xx responses And the ingestion completes successfully without exceeding provider quotas
Timezone and DST Accuracy in Recurrence Expansion
Given recurring events spanning multiple timezones and DST transitions When occurrences are expanded and stored Then each occurrence reflects the correct local start/end based on the event’s timezone rules And UTC-normalized times align with provider-calculated instances And attendee response states are retained at the occurrence level where applicable
Immediate Scheduling Insights Trigger on Completion
Given the 90-day ingestion job completes for an organization When the final checkpoint is committed Then the insights generation process is triggered automatically And initial conflict-free windows and after-hours metrics are available via API and UI within 2 minutes And insights reflect privacy-respected availability and exclude non-imported events
Directory-Aware Identity Matching & De-duplication
"As an admin, I want imported attendees to automatically map to our directory users so that teams appear correctly without manual cleanup."
Description

Leverage Google Directory and Microsoft Graph to resolve attendee identities to canonical organization users, unifying aliases, secondary emails, and cross-tenant records with confidence scoring. Deduplicate users and groups across sources, handle soft-deleted and external contacts, and expose hooks for admin overrides. Produce a clean, unified roster the moment import completes to enable accurate scheduling and analytics.

Acceptance Criteria
Resolve Identities via Directories with Confidence Scoring
Given Google Directory and Microsoft Graph connections are authorized and last 90 days of calendar attendees are available And directory user objects include primary email, aliases/secondary emails, and immutable IDs (Google userId, AAD objectId) When the import job runs Then each attendee whose email or immutable ID matches exactly one directory user must be resolved to a canonical user with confidence score = 1.0 And each attendee whose email matches a directory alias or secondary email for exactly one user must be resolved to that canonical user with confidence score >= 0.95 And attendees with ambiguous matches (more than one candidate) must remain unresolved and be placed in the review queue with the top 3 candidates and their scores And attendees with no directory match must be labeled external = true and not assigned a canonical internal user And the resolution process must be deterministic and idempotent across repeated imports with identical inputs
Unify Aliases and Secondary Emails into Canonical User
Given directory users include alias and secondary email lists When calendar events contain attendee addresses equal to any listed alias or secondary email Then those attendees must map to a single canonical userId And no duplicate canonical user records are created And the canonical user’s primaryEmail remains the directory primary email And the mapping is persisted so subsequent imports do not create new mappings And a mapping view exposes sourceEmail -> canonicalUserId entries for audit
Normalize Cross-Source Users into One Canonical Record
Given the same employee exists in Google Directory and Microsoft Graph And at least one strong key is present: identical primary email OR identical enterprise employeeId OR identical SSO subject/immutable externalId When the import runs Then records meeting any strong key must be unified into one canonical user with merged sourceIds And if conflicting primary emails exist but the employeeId matches, set canonical primaryEmail by precedence order (default Google > Microsoft) unless an admin policy overrides it And the resulting canonical user must retain per-field provenance and compute confidence score >= 0.98 for the unified identity
De-duplicate Users and Groups Across Sources
Given user and group objects are fetched from both sources When de-duplication executes Then the unified roster must contain zero duplicate canonicalUserIds and zero duplicate canonicalGroupIds And all group memberships must reference canonicalUserIds only (no source-specific IDs) And users that appear multiple times across sources are represented exactly once in the final roster And group entities with identical email or externalId are unified, preserving unique members And a de-duplication summary is emitted including counts: usersMerged, groupsMerged, unresolvedEntities
Handle Soft-Deleted Directory Users and External Contacts
Given some directory users are soft-deleted or suspended and some attendees are external contacts not present in any directory When the import runs Then soft-deleted or suspended directory users must be marked inactive = true and excluded from scheduling/fairness rotation And historical event references to those users remain linked to the canonical record for analytics And external contacts are labeled external = true and excluded from deduping with internal users And external contacts may be included in events but never gain canonical internal userIds And a report lists counts of inactive internal users and external contacts identified
Admin Overrides and Audit Trail for Identity Links
Given an admin with Organization Admin role and an unresolved attendee or a mislinked identity exists When the admin uses the override API/UI to link or unlink attendee -> canonical user Then the system applies the override within 30 seconds, re-runs affected resolution, and updates the unified roster And the override persists across subsequent imports until explicitly reverted And an audit log entry is created with actor, UTC timestamp, action, before/after linkage, rationale, and correlationId And an API GET returns current overrides and audit entries filterable by time range and actor And overrides supersede automatic matches regardless of confidence score
Emit Clean Unified Roster at Import Completion
Given import and resolution complete without fatal errors When the job reaches completion state Then a unified roster payload is emitted where each canonical user contains: canonicalUserId, primaryEmail, displayName, sourceIds[], confidenceScore, external, inactive, mergedAliases[] And the payload is available via API and queued to the downstream pipeline within 60 seconds of completion for up to 10,000 attendees And the roster passes schema validation and contains no references to non-canonical IDs And a success metric import.unified_roster.published = true is recorded with counts of totalUsers, internalUsers, externalContacts, inactiveUsers, groups And the process is idempotent so replays within the same import run do not create duplicates
Preference Extraction & Normalization
"As a product manager, I want TimeTether to import users’ working hours and preferences so that recurring meetings are scheduled within fair, acceptable times."
Description

Extract working hours, time zones, focus hours, and no-meeting days from provider signals (e.g., Working Hours, Out of Office, focus time) and normalize them into TimeTether’s preference model. Infer missing data from calendar patterns, allow per-user opt-in for sensitive fields, and surface an audit of inferred values for review. Persist preferences to drive fairness rotation and reduce after-hours meeting load from day one.

Acceptance Criteria
Normalize Provider Signals (Working Hours, Time Zone, Focus, No-Meeting Days)
Given a connected Google Workspace or Microsoft 365 tenant and users with provider Working Hours, Focus Time, Out of Office, and a defined calendar time zone When the One-Click Import job runs for the last 90 days Then the system maps Working Hours to preferences.working_hours {days_of_week, start_time, end_time, time_zone (IANA)} And maps Focus Time blocks to preferences.focus_blocks with exact start/end and recurrence preserved And maps Out of Office (all-day and partial-day) to preferences.no_meeting_days with accurate date ranges and partial-day windows And converts Windows time zones to IANA where applicable and stores time_zone at user level And stores source=provider and last_updated timestamps for all extracted fields And successfully processes users with multiple calendars by prioritizing the primary calendar settings
Infer Missing Preferences from 90-Day Calendar Patterns
Given a user lacks provider Working Hours and/or time zone When analyzing the last 90 days of busy events (excluding all-day OOO, tentative/declined events) Then infer working_hours.start as P20 of first busy event start minus 30 minutes (clamped 05:00–11:00 local) And infer working_hours.end as P80 of last busy event end plus 30 minutes (clamped 15:00–22:00 local) And infer working days as those weekdays with busy events present in ≥60% of the past weeks And infer time_zone as the modal event time zone; if tie/unknown, set org default and mark needs_review And compute confidence = fraction of observed days within inferred window; if confidence < 0.7 or < 10 qualifying days, set activation=false and status=needs_review And label all inferred fields with source=inference and expose them to the audit view
Consent-Gated Capture of Sensitive Fields
Given a user has not granted consent for sensitive fields (focus_blocks, no_meeting_days derived from OOO) When the import runs Then the system does not persist sensitive fields and marks them pending_consent in audit And upon user opt-in via UI or one-click link, the system backfills the last 90 days and persists within 2 minutes And upon consent revocation, the system deletes sensitive fields within 24 hours and halts further collection And all consent actions are logged with user_id, fields, granted/revoked, timestamp, and version And admin roles cannot override the absence of user consent; sensitive fields remain redacted in exports and APIs
Audit Surface and Override Flow
Given an administrator opens the Import Audit view after an import completes When reviewing a user's preferences Then each field displays value, source (provider|inference|user_override|admin_override|verified_inference), confidence (0–1 for inference), and last_updated And the admin can Accept an inferred value (source becomes verified_inference) or Override any value (source becomes admin_override) with a reason And all changes are versioned with actor, timestamp, old_value, new_value And exporting to CSV includes user_id, field, value, source, confidence, last_updated, actor, change_reason And accepted/overridden values propagate to the scheduling engine within 5 minutes
Preference Persistence and Idempotent Re-Import
Given preferences exist for a user from a prior import When a subsequent import runs with no upstream changes Then no preference values change and a no-op entry is recorded in the audit log And when upstream values change, merge priority is user_override > admin_override > provider > verified_inference > inference And user/admin overrides are preserved across imports And re-import does not create duplicate focus_blocks or no_meeting_days entries And each field retains up to 10 versions; older versions are archived and retrievable via audit API
Immediate Use of Preferences by Scheduler
Given preferences are stored or verified for users When a new multi-participant scheduling request is created Then proposed time slots do not violate any participant's working_hours or no_meeting_days (zero after-hours slots) And focus_blocks (if consented) are treated as soft constraints and avoided when alternatives exist And the scheduling engine begins using new/updated preferences within 2 minutes of import completion And scheduling logs include the applied preferences and constraint justifications for each proposed slot
Graceful Degradation on Provider Errors and Rate Limits
Given the provider API returns transient errors or 429 rate limits during import When the import job runs Then the system retries up to 3 times with exponential backoff starting at 30 seconds And completes partial imports, isolating failures to affected users while succeeding others And marks the run status as partial_success and surfaces impacted users/fields in the audit with error codes And leaves unavailable fields unset (no default activation) and notifies the admin via in-app alert And a subsequent manual retry can be initiated and resumes from the last successful checkpoint
One-Click Onboarding UX & Progress Feedback
"As a busy lead, I want a single, clear import action with progress feedback so that I can trust setup will finish without constant oversight."
Description

Provide a single onboarding action to connect the workspace and kick off import, with real-time progress indicators for users discovered, calendars synced, and matches resolved. Run imports as background jobs with pause/cancel, idempotent re-runs, and completion notifications via email/Slack. Present clear error states with guided remediation to minimize admin intervention and shrink setup from hours to seconds.

Acceptance Criteria
One-Click Connect Starts Import and Shows Live Progress
Given a workspace admin on the onboarding screen When the admin clicks "Connect Workspace" and completes OAuth for Google Workspace or Microsoft 365 Then a background import job starts within 5 seconds And the UI displays live counters for Users Discovered, Calendars Synced, and Matches Resolved updating at least every 2 seconds And the counters reflect backend state with ≤1% discrepancy over any 60-second interval And progress state persists across reloads and devices, and the job continues if the browser is closed
Pause, Resume, and Cancel Background Import
Given an active import job When the admin clicks Pause Then the job transitions to Paused within 10 seconds and processing halts without partial writes And when the admin clicks Resume Then processing restarts from the last committed checkpoint without duplicating processed records And when the admin clicks Cancel Then the job stops within 10 seconds, retains committed data, marks status as Canceled, and offers a Restart option And all actions are idempotent, audit-logged with user and timestamp, and reflected in the UI within 2 seconds of the state change
Idempotent Re-run with Directory-Aware Matching and De-duplication
Given a completed import When the admin clicks "Re-run Import" Then the system reprocesses the last 90 days without creating duplicate user, calendar, or preference records (0 duplicates across unique keys) And users are matched to directory identities by email/UPN with consistent mapping across runs And unresolved conflicts are surfaced as conflicts (not auto-merged), while Matches Resolved increases only for deterministic rules applied And if there are no changes since the last run, the job completes in under 2 minutes and displays "No updates" And only one active import job can run per workspace; concurrent triggers coalesce into a single run
Completion Notifications via Email and Slack with Summary
Given email and/or Slack notifications are configured When an import completes with success, warnings, or failures Then a notification is sent within 60 seconds via all configured channels And the notification includes totals for Users Discovered, Calendars Synced, Matches Resolved, Conflict count, duration, and deep links to the import report and remediation actions And if Slack is not connected or delivery fails, email is still sent and an in-app completion banner is shown And the import report opened from the notification matches the metrics shown in the notification
Error States with Guided Remediation
Given a recoverable error occurs (e.g., revoked OAuth, missing scopes, API quota limits, network timeouts) When the backend reports the error Then the UI shows a clear error banner with error code, plain-language summary, and step-by-step remediation guidance And provides one-click actions (Re-authenticate, Request Scopes, Retry Step) where applicable And displays a correlation ID for support and logs the event with context And after successful remediation, clicking Retry resumes from the last safe checkpoint and proceeds without duplications
Performance and Responsiveness Targets for One-Click Onboarding
Given a workspace up to 200 users with 90-day history When OAuth completes Then the connect-and-start flow displays the initial progress screen within 15 seconds And the UI remains responsive with time-to-interactive ≤2 seconds during import And progress counters begin rendering values within 3 seconds of job start And the 95th percentile end-to-end import time is ≤20 minutes, with no UI freezes >1 second
Privacy, Consent, and Data Minimization Controls
"As a security-conscious admin, I want fine-grained permissions and data minimization so that TimeTether meets our compliance requirements."
Description

Enforce least-privilege scopes, admin/user consent where required, and default masking of private event details to busy/free. Provide org policies to restrict data fields, set retention for imported history, and honor data subject requests and revocations. Encrypt data in transit and at rest, maintain audit logs of access and changes, and document permissions to satisfy security and compliance reviews.

Acceptance Criteria
OAuth Scopes Least Privilege Enforcement
Given a new workspace connection attempt, When requesting permissions, Then only the minimum scopes required for free/busy evaluation and basic directory lookup are requested, and no scopes for event bodies, attachments, or email content are requested. Given an integration token without elevated scopes, When the service attempts to access restricted fields, Then the request is denied and a policy-violation audit log entry is recorded with timestamp, client, and scope details. Given scopes are changed in configuration or code, When a new build is deployed, Then admins must complete an explicit re-consent flow before any synchronization resumes, and syncing remains disabled until re-consent is completed.
Admin and User Consent Flow Compliance
Given a tenant that requires admin consent, When a non-admin user attempts to connect, Then the flow is blocked, an admin-consent link is presented, and no data is imported until admin consent is granted. Given admin consent is granted, When an end user connects, Then user consent is captured with user ID, tenant ID, scopes, purpose, and timestamp, and is retrievable for audit. Given a user revokes consent (via provider or in-app), When revocation is received, Then all syncing stops within 60 seconds, tokens are invalidated, and a confirmation record is logged and surfaced to the admin.
Default Calendar Data Masking to Busy/Free
Given default privacy settings, When calendars are imported, Then only start/end time, time zone, organizer, attendee identifiers, and free/busy status are stored, and titles, descriptions, locations, conferencing links, and notes are masked or omitted. Given events marked private or sensitive, When displayed in the UI or used in scheduling logic or notifications, Then they appear as busy/free only with no sensitive content exposed. Given an admin enables an explicit policy to allow additional fields, When toggled on, Then affected users must provide explicit consent before those fields are stored, and the policy state is recorded in audit logs.
Org Policy for Field-Level Data Restrictions
Given an admin policy configuration screen, When the admin disables storage of specific fields (e.g., locations, attendees, titles), Then subsequent imports exclude those fields and existing stored values are redacted within 5 minutes. Given external API or exports are requested, When restricted fields are queried, Then those fields return null or redacted values consistently across endpoints and exports. Given a policy change is saved, When applied, Then an audit entry is recorded capturing actor, fields affected, previous/new values, and timestamp.
Data Retention Controls for Imported History
Given the default retention is 90 days, When an admin updates retention to 30 days, Then calendar-derived data older than 30 days is permanently deleted within 24 hours and the deletion job outcome is logged. Given retention is set to 0 days, When imports run, Then no historical event data is persisted beyond the current scheduling window required for free/busy computation. Given a tenant offboards or deletes the connection, When the action is confirmed, Then all tenant data is purged within 24 hours and an optional export is offered prior to deletion (if enabled by policy).
Honoring Data Subject Requests and Revocations
Given a data subject access request is submitted via UI or API, When processing begins, Then a machine-readable export (JSON) of the subject’s stored calendar-derived data, consents, and audit events is produced within 24 hours and made available to authorized admins. Given a verified deletion request, When executed, Then the subject’s stored data and derived artifacts are purged within 72 hours, caches are invalidated, and future processing is blocked unless new consent is granted. Given consent is revoked at the identity or calendar provider, When the revocation webhook/notification is received, Then processing stops within 60 seconds and tokens are invalidated, with confirmation logged and optionally notified to the admin.
Encryption and Audit Logging Compliance
Given any client-service or service-service communication, When data is transmitted, Then TLS 1.2+ is enforced; connections failing TLS are rejected and the event is logged. Given data is stored in databases or object storage, When persisted, Then AES-256 encryption-at-rest is applied using a managed KMS with key rotation at least every 90 days. Given personal data access occurs, When accessed by a service or admin, Then an immutable audit log entry is recorded with actor, resource, fields accessed, purpose, and timestamp, retained for at least 365 days and exportable in CSV/JSON. Given a compliance review request, When initiated by an admin, Then a permission catalog and data flow documentation reflecting actual configured scopes and data uses is generated and downloadable within 5 minutes.
Resilient Import Pipeline with Rate Limit Governance
"As an operator, I want the import to gracefully handle API limits and failures so that onboarding completes reliably without manual intervention."
Description

Build a fault-tolerant import pipeline with exponential backoff, jitter, and adaptive concurrency to respect Google/Microsoft rate limits. Implement checkpointing, resumable batches, and a dead-letter queue for irrecoverable records, with observability metrics, tracing, and alerts. Provide per-tenant throttling budgets to ensure reliable completion without impacting production limits.

Acceptance Criteria
Adaptive Concurrency Under Provider Rate Limits
- Given active imports across multiple tenants and provider-published quotas, when the 429/QuotaExceeded error rate over any 60-second window exceeds 1% for a tenant, then the system reduces that tenant’s effective concurrency by at least 50% within 10 seconds and maintains provider-facing request rates below quota for the next 5 minutes. - Given stable 2xx success for 5 consecutive minutes with 429 rate below 0.1%, when increasing concurrency, then increments are no more than 10% per minute and never push sustained requests above 80% of the observed per-tenant quota. - Given concurrent tenants, when one tenant spikes traffic, then other tenants’ provider-facing 429 rate remains below 0.5% over any 5-minute window.
Exponential Backoff with Jitter and Retry-After Respect
- Given a 429 or 503 without Retry-After, when retrying, then backoff follows exponential with full jitter: base 1s, factor 2, cap 32s, with a maximum of 6 attempts before surfacing failure. - Given a Retry-After header is present, when retrying, then the next attempt waits at least the Retry-After duration (capped at 60s) with ±50% jitter applied and counts toward the same 6-attempt limit. - Given a permanent 4xx error (e.g., 400 invalid, 403 forbidden non-transient), when encountered, then no further retries are performed and the record is routed to the DLQ within 60 seconds.
Checkpointed, Resumable Batch Imports
- Given a batch import in progress, when the worker process is terminated mid-batch, then upon restart the job resumes from the last committed checkpoint within 60 seconds and reprocesses no more than 1 per 1,000 already-completed records due to overlap. - Given idempotent write semantics, when the same record is retried after a crash, then no duplicate external side effects are produced (verified by unique idempotency keys and unchanged record counts). - Given intermittent network partitions, when up to three interruptions occur at 25%, 50%, and 90% progress, then final completion includes 100% of eligible records with zero data loss.
Dead-Letter Queue for Irrecoverable Records
- Given a record fails with a permanent error after validation, when classified as irrecoverable, then it is published to the DLQ within 60 seconds with payload, error code, last attempt timestamp, tenant ID, and correlation ID; sensitive fields are masked per policy. - Given the DLQ contains items, when an operator triggers a replay, then items are re-enqueued in FIFO order with a fixed backoff, successes are removed from the DLQ, failures remain with attempt count incremented, and metrics for replay outcomes are emitted. - Given DLQ retention policy, when no replay occurs, then items remain available for at least 14 days and are visible in DLQ size and age metrics and in the operator UI list.
Per-Tenant Throttling Budgets and Fairness
- Given configured per-tenant budgets (requests/minute and max concurrency), when multiple tenants import simultaneously, then no tenant exceeds its configured budget and effective throughput per tenant is within ±10% of its budgeted share over any 5-minute window. - Given unused capacity from idle tenants, when redistribution occurs, then active tenants may borrow capacity up to 50% above their budget, and borrowed capacity is preempted back to the original tenant within 2 seconds of that tenant becoming active. - Given a noisy tenant attempts to exceed budget, when observed, then the system sheds excess by queuing or internal throttling without increasing other tenants’ provider-facing 429 rate above 0.5% over any 5-minute window.
Observability: Metrics and Tracing Coverage
- Given the import pipeline runs, when scraping metrics, then the system exposes per-tenant and global metrics at 10-second resolution: requests_total by status, retries_total by reason, backoff_seconds_sum, current_concurrency, queue_depth, job_duration_seconds, records_processed_total, dlq_size, checkpoint_lag_seconds, and provider_api_latency_seconds. - Given a single record is processed end-to-end, when viewing traces, then a distributed trace exists spanning job start, batch fetch, provider API calls, parsing, write, checkpoint, with correlation IDs for tenant and provider; trace sampling rate is configurable with a default of 10%. - Given dashboards are deployed, when opening the Import Reliability dashboard, then it visualizes the above metrics and includes 7-day trends for success rate, 429 rate, DLQ growth, and average job duration.
Alerting on Failures and SLO Breaches
- Given an import job starts, when the configurable 95th-percentile job duration threshold (default 45 minutes) is exceeded over a rolling 1-hour window, then a P2 alert is sent to PagerDuty with links to the dashboard and runbook. - Given provider 429 rate exceeds 1% for 5 consecutive minutes for any tenant, when detected, then a P1 alert is fired and adaptive concurrency reduces within 10 seconds (verified by a drop in current_concurrency metrics). - Given DLQ size increases by more than 100 items within 10 minutes or any DLQ item age exceeds 24 hours, when detected, then a P2 alert is sent to Slack and an incident ticket is created automatically.

Work Window Map

Automatically infers each person’s 90‑day work hours, no‑meeting blocks, and timezone shifts from real activity patterns. Produces humane scheduling windows out of the box, slashing after‑hours risk without manual configuration.

Requirements

Activity Signal Ingestion Connectors
"As a workspace admin, I want TimeTether to connect to our calendars and tools to collect activity metadata so that it can infer accurate work windows without manual setup."
Description

Implement secure, scalable connectors to ingest 90 days of user activity signals that indicate working patterns: calendar events (Google Workspace, Microsoft 365), collaboration presence (Slack, Microsoft Teams), code activity (GitHub, GitLab), issue trackers (Jira), and OS login/idle telemetry where available. Normalize identities via SSO, de-duplicate users across systems, and map events to a unified timeline per user. Support webhook and polling modes, backfill on initial connect, incremental updates, rate-limit handling, retries, and idempotent processing. Store only metadata necessary for inference with encryption at rest, and expose health and throughput metrics.

Acceptance Criteria
Initial 90‑day backfill on first connector authorization
Given a tenant admin connects Google Workspace Calendar, Microsoft 365, Slack, GitHub, GitLab, or Jira for the first time, when ingestion begins, then the system requests and processes activity for the previous 90 calendar days for all in-scope users. Given provider page-size and rate-limit constraints, when backfill executes, then it paginates and respects Retry-After headers, never exceeding provider quota limits. Given backfill completes, then the total unified events stored per source equals or exceeds 99% of the count reported by the providers for the same window, and duplicates are <= 0.1%. Given backfill is in progress, when the status API /ingestion/status is queried, then it returns per-connector percent_complete, estimated_time_remaining, and error_counts. Given OS telemetry is unavailable, when other sources are connected, then backfill completes successfully for the available sources without errors.
Incremental updates via webhook and polling with idempotency
Given a connector with webhook support is enabled (Slack Events, GitHub, Google Calendar push, Microsoft Graph subscriptions), when new activity occurs, then 95th percentile end-to-end ingest latency from provider event to unified store is <= 2 minutes. Given webhooks are unavailable or misconfigured, when polling mode is enabled, then incremental changes are polled at a 5-minute interval and ingested with 95th percentile latency <= 7 minutes. Given duplicate or retried deliveries from providers, when the same event is received multiple times, then exactly one unified event is persisted, verified by stable idempotency keys derived from provider IDs and timestamps. Given out-of-order deliveries, when late events arrive within 7 days, then the unified timeline is updated correctly without creating gaps or duplicates. Given webhook requests are received, when the signature is invalid or timestamp is stale (>5 minutes), then the request is rejected with 401 and no data is processed.
Cross-system identity normalization and de-duplication via SSO
Given the tenant is integrated with SSO (Okta/Azure AD/Google), when connectors are authorized, then provider user identities are mapped to a single internal principal using stable identifiers (SSO subject or verified email), with 100% match on a seeded test fixture of 100 users with consistent emails. Given a person has accounts in multiple systems (e.g., Slack and GitHub) with the same verified email, when events are unified, then only one user appears in the system with a merged timeline. Given conflicting identities (email mismatch) for the same person are detected, when precedence rules are applied (SSO directory > Google Workspace > Microsoft 365 > others), then a single principal is produced and conflicts are logged to an audit stream. Given a user is deactivated in the SSO directory, when the next sync runs, then the user is excluded from future ingestion within 24 hours and flagged inactive in the unified store.
Unified per-user activity timeline mapping and timezone shifts
Given events from Calendar, presence (Slack/Teams), code activity (GitHub/GitLab), issues (Jira), and OS session telemetry are ingested, when timelines are constructed, then each user has a 90-day unified timeline with per-day working windows derived from activity clusters. Given Daylight Saving Time changes or travel-induced timezone shifts, when events contain timezones or locations, then the inferred timezone per day matches a ground-truth test dataset on >= 95% of days, and changes are reflected within 24 hours. Given "no-meeting" blocks are detected (events titled Focus, Deep Work, or with calendar API focusTime flag), when timelines are generated, then these intervals are marked as no-meeting and take precedence over inferred working windows. Given overlapping and conflicting signals, when precedence is applied, then the system prioritizes explicit calendar events over OS telemetry over presence for window boundaries, and the resulting windows are contiguous and non-overlapping.
Rate limit handling and resilient retry with exactly-once semantics
Given a provider returns HTTP 429 or a Retry-After header, when ingestion runs, then the connector backs off according to the header and resumes without exceeding limits, with no permanent data loss. Given transient errors (5xx/timeout) occur, when retries are performed, then exponential backoff with jitter is used up to 6 attempts, after which the event is placed on a dead-letter queue with metadata for reprocessing. Given the same page or event is fetched multiple times due to retries, when events are written, then idempotent upserts prevent duplicates, verified by invariant: count(unique_event_ids) == count(rows) in the unified store for a replay test. Given a prolonged outage of 1 hour, when connectivity is restored, then backlog lag (max event timestamp to now) returns to < 10 minutes within 60 minutes.
Security and privacy controls for ingestion and storage
Given data is stored, when records are written, then only metadata necessary for inference is persisted per source: Calendar {start_at, end_at, attendee_count, organizer_domain, busy/free, source_id}; Presence {status, start_at, end_at, source_id}; Code {commit_timestamp, repo_id, author_id, source_id}; Issues {event_timestamp, project_id, actor_id, transition_type, source_id}; OS {login_at, logout_at, idle_intervals, device_id}; no titles, descriptions, message bodies, or diffs are stored. Given data at rest, when verified by configuration, then storage is encrypted using AES-256 with cloud KMS-managed keys; key rotation occurs at least annually; all tokens/secrets are encrypted and access is restricted by least-privilege IAM roles. Given OAuth scopes are requested, when connectors are installed, then only least-privilege scopes sufficient for metadata access are used, documented per provider, and installs succeed in a test tenant with security scanning passing. Given webhook endpoints receive traffic, when requests are processed, then signatures are validated (provider-specific HMAC or signing cert), TLS 1.2+ is enforced, and unsigned or replayed requests are rejected.
Operational observability and metrics for connectors
Given the system is running, when the /metrics endpoint is scraped, then per-tenant and per-connector metrics are exposed: events_ingested_total, events_ingested_per_minute, error_rate, backlog_size_seconds, last_success_timestamp, webhook_delivery_latency_seconds, polling_cycle_duration_seconds. Given a connector is healthy, when the /connectors/health endpoint is queried, then it reports status=UP with last_sync_time within SLA (<= 10 minutes for active tenants); unhealthy connectors report status=DOWN with reason. Given ingestion errors exceed 1% over 5 minutes for any connector, when alerting rules are active, then an alert is fired to the on-call channel with connector, tenant, and error samples. Given dashboards are provisioned, when an operator views the ingestion overview, then they can see trend charts for throughput, error rate, and lag over the last 24 hours for each connector.
Work Window Inference Engine
"As a team lead, I want the system to automatically infer each person’s typical working hours so that meetings are proposed during humane, realistic times."
Description

Create a probabilistic engine that converts multi-source activity timelines into humane scheduling windows per user for the past 90 days. Build weekday/hour histograms, detect active vs. inactive intervals, and derive typical start/end times with variability bands. Produce a normalized availability model (timezone-aware) including preferred meeting hours and hard exclusions, auto-updating nightly and on new data. Emit machine-readable windows to the core scheduling service and version changes for auditability. Provide tunable thresholds (e.g., minimum activity density, outlier rejection) and safeguards to avoid bias from atypical weeks.

Acceptance Criteria
Histogram and Activity Interval Detection
Given 90 days of timestamped events from multiple sources (calendar, messaging, commits, system activity), When the engine runs for a user, Then it generates per-weekday 24-bin hourly histograms with total and per-source counts. Given configured min_activity_density and bin size of 15 minutes, When classifying bins, Then each bin is labeled active or inactive and persisted with a confidence score. Given the reference validation dataset, When evaluating active/inactive labels, Then precision >= 0.85 and recall >= 0.85. Given missing days or gaps, When constructing histograms, Then missing data does not create false active bins and is marked as null coverage in metadata.
Typical Start/End with Variability Bands
Given >= 30 active weekdays in the last 90 days after outlier rejection, When computing typical start/end per weekday, Then the engine outputs median_start and median_end times with p10–p90 variability bands. Given the reference dataset with ground truth bounds, When comparing median_start and median_end, Then median absolute error <= 30 minutes for each weekday. Given variability bands, When assessing coverage, Then at least 80% of observed active days fall within the p10–p90 band. Given outliers (z-score > 2.5 or IQR rule), When deriving statistics, Then those days are excluded from the computation and recorded in audit metadata.
Timezone Normalization and Shift Detection
Given activity that spans DST changes or travel, When normalizing timestamps, Then daily windows are expressed in the correct local date/time with the appropriate timezone offset for that day. Given timezone shifts >= 2 hours within the period, When building windows, Then the engine segments the model into contiguous date ranges with distinct timezone contexts and merges them without crossing midnight incorrectly. Given the reference scenario, When comparing to a naive UTC model, Then after-hours minutes in the inferred windows decrease by >= 50%. Given conflicting source timezone hints, When resolving, Then the engine applies a deterministic precedence order and records the chosen source and offset in the audit log.
Preferred Meeting Hours and Hard Exclusions Extraction
Given recurring no-meeting blocks (e.g., Focus, No Meetings, 1:1-free) and OOO/PTO events, When inferring availability, Then these intervals are emitted as hard_exclusions with zero schedulable minutes. Given active intervals minus hard_exclusions, When computing preferred meeting hours, Then the engine emits preferred windows within typical start/end that avoid lunch/low-activity troughs detected from histograms. Given configured min_preferred_minutes_per_weekday (e.g., 120), When data supports it, Then each weekday has at least that many preferred minutes; otherwise a data_sparsity flag is set and emitted. Given company holidays from the calendar, When generating windows, Then those dates have no preferred windows and are marked hard_exclusion=holiday.
Nightly and Event-Driven Updates
Given a nightly schedule at 02:00 local time, When the batch job runs, Then 99% of user models complete by 03:00 with a success rate >= 99% and retries for transient failures. Given arrival of new source data for a user, When an event is received, Then the affected model is recomputed within 5 minutes at the 95th percentile and version incremented. Given any model change, When persisting, Then an audit record captures prior vs new windows, diff summary, triggering event, thresholds in effect, and timestamp. Given no data changes, When the nightly job runs, Then no version change occurs and the engine emits no-op heartbeats only.
Machine-Readable Windows Emission and Contract Compliance
Given a normalized availability model, When emitting to the scheduling service, Then the payload conforms to schema v1.0 and includes user_id, date, windows[{category in {preferred, soft, hard_exclusion}, start, end, tz_offset}], model_version, and metadata. Given transient 5xx from the scheduling service, When sending, Then the engine retries with exponential backoff up to 5 attempts and guarantees at-most-once delivery per (user_id, date, model_version). Given a 4xx schema error, When detected, Then the release is halted, an alert is raised, and the offending payload is quarantined with a link in the audit log. Given a 30-day backfill, When replaying emissions, Then the scheduling service acknowledges 99.9% with HTTP 200 and no duplicate windows are created.
Threshold Tuning and Atypical Weeks Safeguards
Given configurable thresholds (min_activity_density, outlier_rejection_method, min_active_days, holiday_calendar_id), When an admin updates them, Then changes are validated, versioned, and take effect on the next rebuild with entries in the audit log. Given weeks tagged as PTO or with total activity < 50% of the user’s median week, When computing windows, Then those weeks are excluded from statistics to prevent bias. Given insufficient data (< min_active_days, default 20), When inferring, Then the engine falls back to conservative defaults (e.g., preferred 10:00–15:00 local time; hard_exclusions = OOO/holidays/no-meeting blocks) and sets a low_confidence flag. Given the reference cohort, When comparing users with sparse vs dense data, Then average after-hours minutes in inferred preferred windows for sparse-data users is within +10% of dense-data users.
Timezone and DST Shift Detection
"As a frequent traveler, I want TimeTether to recognize when my timezone changes so that my inferred work hours remain accurate while I’m on the road."
Description

Detect and track per-user timezone changes and daylight saving transitions over the 90-day window using calendar event offsets, device/IP hints, and travel indicators. Maintain a dated timeline of timezone assignments and apply the correct offset when synthesizing daily work windows. Distinguish brief anomalies from sustained shifts with hysteresis, and auto-reconcile conflicts. Update windows within hours of a confirmed shift and surface shift history and next expected DST changes to the scheduler.

Acceptance Criteria
Sustained Timezone Shift Confirmation via Multi-Signal Evidence
Given a user’s baseline timezone is recorded And a new UTC offset is observed on calendar event start times for ≥3 distinct events across ≥2 days And corroborating signals include at least one of: device OS timezone or IP geolocation matching the new timezone And the new offset persists for ≥48 consecutive hours When the shift detection job runs Then the system confirms a timezone shift to the new IANA timezone And records a timeline entry with effectiveFrom UTC timestamp = first observed time of the sustained offset And includes the signal sources and a confidence score ≥ 0.8 And subsequent computations use the new timezone from effectiveFrom onward
Conflict Resolution and Auto-Reconciliation of Timezone Signals
Given conflicting timezone signals are present within the same 24-hour window When reconciliation runs Then the system ranks signals by source priority: Calendar event timezone (self-authored) > Device OS timezone > Calendar (attendee) > IP geolocation > Travel indicators And selects the timezone with the highest-ranked signal corroborated by at least one additional signal or ≥3 matching calendar events And if no timezone meets corroboration rules, no shift is confirmed and the baseline is retained And the decision, inputs, and outcome are logged with a correlation ID
Hysteresis Filters Brief Anomalies
Given a new offset is observed for <24 hours or across fewer than 2 consecutive local business days Or only a single weak signal supports the new offset When detection runs Then the system classifies the evidence as a transient anomaly And does not create a timezone assignment or modify work windows And the anomaly is tracked with start/end times for trend analysis
Correct Offset Application in Work Window Synthesis
Given a timeline of timezone assignments with effectiveFrom timestamps over the last 90 days When generating daily work windows for any day D Then for any moment t within D, the UTC offset applied is taken from the assignment where effectiveFrom <= t < next effectiveFrom And if a work window spans an offset boundary, the window is split at the boundary and mapped to UTC using the respective offsets And no window is shifted into local hours outside the configured humane window due to offset application And the produced windows are deterministic given identical inputs
Timely Update After Confirmed Shift
Given a timezone shift has been confirmed When confirmation occurs Then the user’s work windows, caches, and scheduler availability are recalculated using the new timezone within 2 hours And a timezoneShiftConfirmed event with payload (userId, oldTz, newTz, effectiveFrom) is published within 5 minutes And the scheduler reflects the new offset in availability searches within 2 hours
Surface Shift History and Next Expected DST to Scheduler
Given the scheduler queries the user availability API for shift metadata When requesting history for the past 90 days and forecast for the next 180 days Then the API returns an ordered list of timezone assignments (tz, offset, effectiveFrom, effectiveTo) covering the past 90 days And returns next expected DST transition (utcTimestamp, tz, direction) computed from tzdb for the current timezone And values are updated within 15 minutes of any change And timestamps are ISO 8601 UTC and timezones are valid IANA identifiers
Automatic DST Transition Handling
Given a user’s timezone has a scheduled DST transition at time T per tzdb When time T is reached Then the system updates the user’s UTC offset by the correct ±60 minutes at T And creates a timeline assignment effectiveFrom = T with the new offset And splits any affected work windows on the day of transition so local hours remain consistent And no multi-signal confirmation is required for DST transitions
No‑Meeting Block Mining
"As an individual contributor, I want my recurring focus times and breaks to be respected automatically so that I’m not interrupted by meetings during those periods."
Description

Infer recurring no-meeting blocks (e.g., lunch, school pickup, deep work, team-wide quiet hours) from patterns like calendar event titles/categories, Slack DND schedules, and lack of meeting acceptance during certain periods. Incorporate company holidays and regional observances from connected calendars. Generate hard and soft exclusion blocks with repeat rules and sync them to the scheduling constraints, reducing after-hours risk and interruptions without manual configuration.

Acceptance Criteria
Infers Lunch No-Meeting Block from Calendar Patterns
Given 90 days of connected calendar data with event titles and categories When events with titles or categories matching lunch/break keywords recur on ≥60% of weekdays between 11:00–14:00 local time for ≥4 consecutive weeks, clustered 30–90 minutes Then a recurring soft exclusion block is inferred at the modal start time with median duration and an RRULE for weekdays And the block is labeled "Lunch" with source=CalendarTitle and confidence ≥0.8 And meeting proposals do not overlap this block unless no conflict-free alternative exists across all participants And any overlap is explicitly marked as a soft conflict requiring host override
Creates Hard Exclusions from Slack DND Schedule
Given a connected Slack workspace with user DND schedules and permissions granted When a user has a repeating DND window on ≥3 days/week for ≥3 consecutive weeks or a workspace-level DND policy Then a hard exclusion block is created mirroring each DND window with an appropriate RRULE and source=SlackDND And scheduling proposals include 0 times that overlap these hard exclusions And changes to Slack DND are reflected in TimeTether within 60 minutes
Derives Soft Exclusions from Low Acceptance/Decline Patterns
Given 90 days of invite/response history across connected calendars When a contiguous 30–120 minute window shows ≤10% acceptance and ≥80% declines/no-response across ≥6 occurrences over ≥4 weeks, excluding holidays and off-hours Then a recurring soft exclusion block is inferred for that window with source=DeclinePattern and confidence ≥0.7 And the scheduling engine applies a penalty so that this window is proposed only if no equivalent alternative exists within participants’ work windows And the block includes an audit summary listing evidence intervals and counts
Applies Company Holidays and Regional Observances
Given connected company and regional holiday calendars and each user’s region When a date or partial day is marked as a holiday/observance for a user or company Then a hard exclusion is created for the affected period with source=Holiday and propagated to all impacted users And the scheduler proposes 0 times overlapping the exclusion for impacted users; mixed-region proposals avoid the holiday for those users And updates to holiday calendars are reflected within 24 hours
Generates Repeat Rules and Classifies Hard vs Soft
Given any inferred exclusion block When the block is saved Then it is persisted with an iCalendar-compliant RRULE and EXDATEs for detected anomalies, honoring DST and timezone shifts And classification rules are applied: Holiday and SlackDND => hard; Lunch and DeclinePattern => soft (unless user promotes to hard) And each block stores source, confidence, timezone, severity, and next 5 occurrences for verification
Syncs Exclusions to Scheduling Engine and Invite Flow
Given one or more inferred exclusion blocks exist for participants When generating meeting options or one-click invites Then hard exclusions are never included; soft exclusions are included only when no non-overlapping option exists that meets fairness constraints And new/updated blocks are reflected in option generation within 5 minutes of creation/update And proposed times shown in UI are annotated with "hard conflict" or "soft conflict" labels when applicable
Explainable Review and Override UI
"As a manager, I want an interface to review and adjust inferred windows for my team so that we can quickly correct edge cases and keep schedules accurate."
Description

Provide a calendar-style overlay that visualizes inferred work windows, no-meeting blocks, timezone assignments, and confidence per day. Offer one-click accept, edit, or override at user and admin levels, with reason capture and immediate propagation to scheduling. Show lightweight explanations (e.g., “Based on 78% activity between 9:30–17:30 over the last 6 weeks”) and an audit trail of changes. Support bulk review for teams and rapid rollback to prior inferred versions.

Acceptance Criteria
Overlay Visualization of Inferred Work Windows with Confidence
Given a user opens the Explainable Review UI for their 90-day period, When the overlay renders, Then for each calendar day it displays: inferred work window (start–end), no-meeting blocks, timezone assignment, and a confidence score from 0% to 100%. Given a day has confidence below 50%, When displayed, Then the UI marks it as low confidence with a distinct visual indicator and an accessible label containing the phrase Low confidence. Given the user hovers or taps a day segment, When the explanation appears, Then it includes a numeric activity percentage, a time range, and a lookback window description. Given the user adjusts the visible date range up to 180 days, When the range changes, Then the overlay updates within 1000 ms on first load and within 500 ms on subsequent loads. Given the user toggles the timezone display, When toggled, Then all day labels and ranges re-render in the selected timezone without changing the underlying assignments.
One-Click Accept of Inferred Schedule by User
Given an inferred schedule is displayed, When the user clicks Accept for a single day, Then the day is marked Accepted, persisted, and the Accept control for that day becomes disabled. Given an inferred schedule is displayed, When the user clicks Accept for the current week, Then all inferred days in the week are marked Accepted in a single atomic operation. Given an acceptance action completes, When propagation occurs, Then the scheduling engine reflects the accepted windows within 5 seconds. Given a network failure during accept, When the request fails, Then the UI shows an error and no days are marked Accepted. Given a day is already Accepted, When the user clicks Accept again, Then the action is idempotent and no duplicate audit entries are created.
Edit and Override with Reason Capture
Given the user has permission to edit their schedule, When they click Edit on a day, Then they can modify start and end times in 15-minute increments, adjust no-meeting blocks, and update timezone assignment. Given the user modifies any field, When they click Save, Then a Reason field is required and Save is disabled until a non-empty reason is provided. Given a valid edit with a reason, When Save is confirmed, Then the override is persisted, an audit entry capturing before and after values and reason is created, and the overlay reflects the change within 1 second. Given overlapping no-meeting blocks are entered, When validation runs, Then the user sees an error and cannot save until overlaps are resolved. Given an override is active for a day, When displayed, Then the day is labeled Overridden and shows the reason on hover or tap.
Admin Bulk Review and Apply for Team
Given an admin opens Team Bulk Review, When the grid loads, Then it lists team members with per-day confidence and provides filters for Low Confidence and Timezone Shift. Given an admin selects 10 to 200 users and a date range, When Apply Accept is clicked, Then the accept operation is applied to all selected users and completes within 10 seconds, with per-user success or failure indicators. Given an admin applies a bulk override template, When executed, Then a reason is required and stored for each affected user-day. Given a subset of users fails due to permission or validation, When the bulk job completes, Then failures are reported with actionable error messages and unaffected users remain unchanged. Given a non-admin accesses Bulk Review, When the page loads, Then bulk actions are not visible and access is denied with an appropriate message.
Immutable Audit Trail of Changes
Given any accept, edit, override, or rollback occurs, When the action completes, Then an immutable audit entry is created with actor, UTC timestamp, scope (user/day or team/date range), before and after values, reason, and source (UI or API). Given a user opens the Audit Trail, When results load, Then entries appear in reverse chronological order with filtering by actor, user, date range, action type, and confidence band. Given an audit entry is selected, When viewed, Then it displays a field-level diff of the changes and the exact values persisted. Given an export is requested for the last 90 days, When generated, Then a CSV is downloaded within 5 seconds containing all visible columns. Given the system stores 90 days of audit history, When querying that period, Then 100% of entries are retrievable.
Rapid Rollback to Prior Inferred Version
Given prior inferred versions exist for a day or week, When the user clicks Rollback, Then a list of prior versions with timestamps and confidence is shown. Given the user selects a prior version and provides a reason, When Confirm is clicked, Then the current overrides are replaced with the selected version, an audit entry is created, and the overlay updates within 1 second. Given a rollback completes, When propagation occurs, Then the scheduling engine reflects the rolled-back windows within 5 seconds. Given there are no prior versions, When Rollback is opened, Then the control is disabled with a tooltip indicating no versions available. Given an admin selects multiple users, When Bulk Rollback is run, Then the operation applies to all selected users and returns per-user success or failure statuses.
Confidence Scoring and Shift Alerts
"As a user, I want to be notified when the system is unsure about my hours so that I can confirm or correct them quickly."
Description

Compute a confidence score for each user’s inferred windows based on signal density, recency, and consistency. Trigger notifications (email/Slack) when confidence drops below a threshold or when a significant shift is detected (e.g., >90 minutes change in start time or new timezone). Provide one-click confirm/deny actions that feed back into the inference engine, with graceful degradation (e.g., widen windows) when confidence is low.

Acceptance Criteria
Confidence Score Computation
- Given 90 days of activity data with ≥4 active weekdays per week, median start-time variance ≤30 minutes, and last activity ≤7 days ago, When the engine computes the confidence score, Then the score is ≥80/100 and includes factor contributions for density, recency, and consistency. - Given 90 days of activity data with ≤2 active weekdays per week or last activity >21 days ago, When the engine computes the confidence score, Then the score is ≤40/100 and a "stale-data" flag is included. - Given 90 days of activity where start-time variance ≥120 minutes across weeks, When the engine computes the confidence score, Then the score is ≤50/100. - Given an API/UI request for a user's confidence, When the score is returned, Then the payload contains: score (0–100), updatedAt (UTC ISO-8601), factors {density, recency, consistency} each 0–100, and lookbackDays=90.
Low-Confidence Threshold Alerts
- Given the workspace threshold is 60 and a user's score crosses from ≥60 to <60, When the score is persisted, Then a Slack and email alert are sent within 10 minutes containing user, score, threshold, reason summary, and one-click Confirm/Deny actions. - Given a low-confidence alert was sent in the last 72 hours and no new significant shift was detected, When the score remains < threshold, Then no duplicate alert is sent (cooldown 72h). - Given the user has opted out of email or Slack, When the alert is generated, Then only the allowed channels are used. - Given the score drops by ≥10 points again during the cooldown, When evaluated, Then a new alert is sent and the cooldown restarts.
Significant Shift Detection Alerts
- Given the 7-day rolling median start time shifts by >90 minutes relative to the prior 14-day baseline, When detection runs (every 15 minutes), Then a "significant shift" alert is sent within 15 minutes including before/after times and suggested window update. - Given the detected local timezone offset changes by ≥60 minutes and persists for ≥60 minutes, When detection runs, Then a timezone shift alert is sent within 15 minutes. - Given a shift was confirmed by the user in the last 24 hours, When the detector runs, Then no duplicate shift alert for the same dimension is sent.
One-Click Confirm/Deny Feedback Loop
- Given a user receives an alert with one-click actions, When they click Confirm, Then the proposed window change is applied, the action is acknowledged in-channel within 5 seconds, and the confidence score is recomputed within 60 seconds. - Given a user clicks Deny, When processed, Then the proposed change is rejected, a feedback record with reason=deny is stored, and the confidence score is recomputed within 60 seconds. - Given a tokenized action link, When accessed after 7 days or after prior use, Then the request is rejected as expired or already processed without changing state, and the user is offered a safe fallback URL. - Given the same Confirm/Deny is retried, When processed, Then the operation is idempotent and returns the existing final state.
Graceful Degradation at Low Confidence
- Given a user's confidence score is < threshold, When scheduling windows are generated, Then the windows are widened by +60 minutes on both start and end, capped at +120 minutes total expansion, and labeled "Low confidence". - Given widened windows would extend beyond 07:00–19:00 local, When generating availability, Then availability is clamped to 07:00–19:00 local to avoid after-hours meetings. - Given a user's confidence score recovers to ≥ threshold and stays there for 48 consecutive hours, When windows are regenerated, Then the widening is removed and the label is cleared.
Admin Thresholds, Preferences, and Audit Log
- Given an admin accesses settings, When configuring thresholds, Then a workspace default threshold (30–90) can be set and per-user overrides applied. - Given an admin configures notifications, When saved, Then channels (email, Slack), alert cooldown (24–168h), and significant-shift sensitivity (60–120 minutes) are adjustable. - Given any alert or action occurs, When viewing the audit log, Then an entry exists with timestamp, actor, channel, event type, and outcome, retrievable via API and UI.
Data Privacy and Consent Controls
"As a security officer, I want granular consent and data controls so that TimeTether can infer schedules without violating our privacy and compliance policies."
Description

Enforce explicit user/admin consent flows and least-privilege OAuth scopes for all connectors. Display transparent data usage summaries, anonymize or redact content, and retain only metadata needed for inference with a 90-day rolling window. Support per-user opt-out, data deletion on request, regional data residency, encryption in transit/at rest, and audit logging. Provide configuration toggles to exclude specific sources (e.g., code commits) and ensure compliance with SOC 2/GDPR requirements.

Acceptance Criteria
Explicit Consent and Least-Privilege OAuth per Connector
Given a user or org admin initiates connecting a data source (e.g., Google Calendar, Slack, GitHub) When the consent screen is displayed Then it enumerates the exact scopes requested, data categories accessed, purpose (Work Window Map inference), retention period (90-day rolling), and processing region And the user/admin must explicitly approve; no pre-checked boxes are allowed And the connection remains blocked until consent is granted And only least-privilege scopes that permit metadata-only access are requested; broader scopes are rejected with an explanatory message And a consent record capturing subject ID, connector, scopes, policy version, actor, timestamp, and IP is written to the audit log
Transparent Data Usage Summary and Privacy UI
Given an authenticated user opens Settings → Privacy & Data When viewing the Data Usage Summary Then the UI shows per-connector data categories, purposes, legal basis, retention window, residency region, processors, and last sync time And the user can download a machine-readable summary (JSON/CSV) of their stored metadata And the UI displays the current consent policy version and a change history with timestamps
Data Minimization, Redaction, and 90‑Day Rolling Retention
Given ingestion occurs from any supported connector When data enters the processing pipeline Then message/commit/issue bodies and file contents are discarded or redacted before any persistence And only fields required for inference (timestamps, event type, duration, timezone offset, anonymized participant IDs, hashed repo/project identifiers) are stored And identifiers are salted/peppered and cannot be reversed without privileged access And records older than 90 days are purged automatically at least daily And verification queries return zero records older than 90 days across all storages (primary, cache, analytics) And attempts to access original content bodies return none
Per-User Opt-Out and Source Exclusion Toggles
Given a user navigates to Settings → Privacy & Data When the user toggles off specific sources (e.g., code commits, chat activity) Then ingestion and inference exclude those sources within 15 minutes and the change is logged with user, source, time And the Work Window Map continues operating using remaining sources without errors or degraded availability And when the user opts out entirely, all future ingestion for that user halts within 15 minutes and is logged; reinstating opt-in resumes ingestion only from that point forward
User-Initiated Data Deletion (Right to Erasure)
Given a user or admin submits a deletion request for a user’s data When the request is confirmed via a second factor or privileged admin action Then all stored metadata and derived features for that user are deleted across primary storage, caches, and analytics within 24 hours And subsequent inference jobs exclude the deleted user And an auditable deletion receipt detailing scope, systems affected, and completion timestamp is available to the requester And repeated deletion requests are idempotent and safely report no remaining data And backups remain encrypted and are pruned per retention; if immediate physical deletion is not possible, cryptographic erasure (key revocation) is executed and logged
Regional Data Residency and Encryption Controls
Given an org admin selects a residency region (e.g., EU or US) When data is ingested and processed Then storage and processing occur only within the selected region and cross-region transfers are prevented by policy and network controls And data in transit uses TLS 1.2+ with HSTS; non-TLS/legacy protocols are rejected with 4xx/5xx And data at rest uses KMS-managed AES-256 keys with rotation at least every 90 days; backups are encrypted with the same controls And disaster recovery and failover remain confined to the selected region And region-tagged audit logs provide evidence of residency for each event
Comprehensive Audit Logging and Compliance Readiness (SOC 2/GDPR)
Given any privacy-relevant event occurs (consent, scope change, ingestion, inference run, opt-out, deletion, export, region change) When the event is processed Then an immutable, append-only audit entry is written with who, what, when, where (IP/region), purpose, result, and consent policy version And logs exclude content bodies and sensitive payloads while retaining necessary metadata And admins can query and export logs by user, connector, event type, and time range And log timestamps are NTP-synchronized and entries are tamper-evident (hash chain or WORM storage) And automated control mappings demonstrate coverage for SOC 2 CC and GDPR principles (lawfulness, purpose limitation, data minimization, storage limitation, integrity/confidentiality)

FairStart Rotation

Prebuilds a fairness‑driven rotation for the next quarter at connect time, balancing regions, roles, and daylight savings. Everyone sees a predictable, equitable cadence from day one—no spreadsheets, no guesswork.

Requirements

Participant Segmentation & Weighting Rules
"As a program manager, I want to configure how regions and roles are balanced so that the rotation spreads early/late meetings fairly across the team."
Description

Define and manage configurable fairness dimensions that drive rotation decisions, including region, role, seniority, and individual preference weightings. Ingest attributes from directory/calendar sources and user profiles to classify participants, then apply adjustable weights to distribute early/late meeting burdens equitably. Provide admin controls to tune segment weights, pin or exclude participants, and set must-avoid windows. Validate that each cohort has viable overlaps before generation and expose a rules engine interface the scheduler uses when constructing the quarterly plan.

Acceptance Criteria
Directory and Calendar Attribute Ingestion & Segmentation
Given org-level connections to directory and calendar sources are authorized and a sync window opens When the scheduled attribute sync runs Then all active users in the selected scope are upserted with region, role, seniority, and meeting-preference attributes And each participant is classified into zero or more segments within 5 minutes of sync completion And attribute changes update segment membership and create a versioned audit entry with actor, timestamp, and diff And missing attributes apply configured fallbacks and are flagged for review in the admin UI And no duplicate participant records are created (unique user_id enforced) And the latest segmentation snapshot is available to the rules engine API within 1 minute of classification
Admin Configures Segment Weights with Impact Preview
Given an org admin opens Rules > Segmentation Weights When the admin adjusts weights for region, role, seniority, and individual preference dimensions Then the weight vector is validated and normalized (dimension weights sum to 1.0 ± 0.001) And a preview computes projected early/late burden by cohort for the next quarter using current participants and work windows within 3 seconds And invalid configurations (e.g., negative weights, disabled required dimension) block Save with inline errors And on Save, changes are applied atomically, versioned (major.minor.patch), and attributed to the actor with a required change note And API consumers see the new rules_version within 30 seconds, and rollback to any prior version is available
Pin and Exclude Participant Controls
Given an admin pins a participant to defined time windows and/or marks a participant as excluded from the rotation When the rules engine evaluates candidate slots for a quarterly plan Then pinned participant windows are treated as hard constraints and reserved for that participant’s meetings And excluded participants are not assigned to any meeting occurrences And violations caused by conflicting constraints are rejected with explicit reason codes and affected entities And all pin/exclude changes are logged with actor, timestamp, scope, and justification
Must-Avoid Windows Enforcement Across Time Zones and DST
Given per-participant and cohort-level must-avoid windows are configured in local time When constructing candidate slots that span multiple time zones and daylight saving transitions Then no meeting occurrence is scheduled within any must-avoid window (0 violations) And time calculations honor each locale’s DST rules, mapping local windows to UTC accurately And if constraints render a cohort unschedulable, generation is aborted and a conflict report lists the blocking windows and participants
Pre-Generation Cohort Overlap Viability Check
Given cohorts are defined and work-hour windows are configured for all participants When an admin clicks Validate Viability for a specified cadence (e.g., weekly, 60 minutes) and quarter Then the system computes at least one viable overlap per planned occurrence plus a 20% buffer for every required cohort, respecting must-avoid windows and pins And the result returns Pass with counts per cohort, or Fail with named cohorts and primary blocking constraints And validation completes within 60 seconds for orgs up to 1,000 participants and caches results until underlying inputs change
Scheduler-Rules Engine Contract and Determinism
Given the scheduler requests evaluation with inputs {participants, work windows, weights_version, cadence, duration, start_date, constraints} When calling POST /rules/evaluate Then the response includes {rules_version, data_snapshot_id, evaluation_time, reasons[], candidate_slots[]} and is deterministic for identical inputs and snapshot And p95 latency is <= 800 ms for cohorts <= 50 participants and <= 2.5 s for cohorts <= 200 participants And backward-compatibility is maintained for the last two minor versions; requests specifying an unsupported version receive 400 with guidance And 5xx error rate is < 0.1% over a rolling 24h; 429s include Retry-After and an idempotency key is honored for safe retries
Quarterly Rotation Generation Engine
"As an engineering lead, I want TimeTether to generate a complete, fair schedule for the next quarter at setup so that the team has a predictable cadence without manual spreadsheets."
Description

Generate a deterministic, fairness-driven schedule for the next 13 weeks at connect time using inputs from segmentation, availability, and policies. Rotate start times and participant burdens across regions and roles, honoring individual work windows, meeting duration, frequency, and target cadence. Use a seeded algorithm to ensure reproducible results and to enable safe re-generation after edits. Produce a versioned schedule with per-instance metadata (local time per attendee, fairness tags, rule decisions) ready for review, metrics, and invite dispatch.

Acceptance Criteria
Deterministic Seeded Schedule Generation
Given a fixed input set (participants, regions, roles, work windows, availability, frequency, duration, policies) and seed "S" When the engine generates the 13-week rotation Then the full schedule output (instance count, instance ordering, startUTC/endUTC per instance, fairnessTags, ruleDecisions, and instanceIds) is identical across repeated runs Given the same inputs and a different seed "S2" When the engine generates the rotation Then at least one instance startUTC or rotation ordering differs from seed "S" output And all other acceptance criteria constraints validate successfully
Fairness Rotation Across Regions and Roles
Given participants grouped by region (R groups) and role (K groups) When the rotation is generated for T scheduled weeks Then each region appears as fairnessTags.regionOwner either floor(T/R) or ceil(T/R) times, with the max difference between any two regions ≤ 1 And each role appears as fairnessTags.roleOwner either floor(T/K) or ceil(T/K) times, with the max difference between any two roles ≤ 1
Honor Work Windows and Avoid Conflicts
Given availability calendars and declared work windows for all required attendees When an instance is generated Then the local start time for every required attendee is within that attendee's work window (inclusive) And the instance does not overlap any busy event of any required attendee (0-minute tolerance) And if the target slot conflicts, the engine selects the next feasible slot in the same week and records ruleDecisions includes "AlternateSlotChosen" And if no feasible slot exists in the week, the instance is emitted with unschedulable=true and ruleDecisions includes "NoFit"
Frequency, Duration, and Cadence Compliance
Given a meeting frequency f (in weeks) and duration D (minutes) When generating over the 13-week horizon Then the number of instances N equals ceil(13 / f) And each instance has duration exactly D minutes And for each consecutive pair of instances, the start time delta is in [(f weeks − 1 day), (f weeks + 1 day)] And any deviation > 0 days is logged with ruleDecisions includes "CadenceShift"
Daylight Savings and Timezone Handling
Given any attendee whose timezone observes a DST transition within the quarter When generating the instance(s) in the transition week(s) Then each required attendee's local start remains within their current work window after applying DST rules And the engine adjusts UTC time as needed while preserving fairnessTags.regionOwner and fairnessTags.roleOwner for that week And ruleDecisions includes "DSTAdjust" for any affected instance And no attendee's local start shifts by > 60 minutes compared to the immediately prior instance unless "DSTAdjust" is present or the fairness rotation owner changes
Versioning and Metadata Completeness
Given a generation event that produces a schedule When the schedule is persisted Then scheduleVersion increments by 1 versus the last persisted version (or equals 1 if none exists) And every instance includes non-null: instanceId, scheduleVersion, weekIndex, startUTC, endUTC, perAttendeeLocalStart, perAttendeeTimeZone, fairnessTags.regionOwner, fairnessTags.roleOwner, fairnessTags.rotationIndex, ruleDecisions[] And schema validation passes for 100% of instances; otherwise generation fails and no partial schedule is persisted
Stable Re-generation After Edits
Given a previously generated schedule with seed "S" and user edits that modify only a subset of inputs When re-generating with the same seed "S" Then any instance whose inputs are unchanged retains identical instanceId, startUTC, endUTC, fairnessTags, and ruleDecisions And only instances impacted by the edits are modified And changed instances include ruleDecisions contains "RegeneratedDueToEdit" And scheduleVersion increments and a machine-readable diff lists added, changed, and removed instances
Timezone & Work Window Aggregation
"As a distributed team member, I want my local work hours and holidays respected so that meetings aren’t scheduled outside reasonable times."
Description

Automatically detect participant time zones, default working hours, and regional holidays from connected calendars and profiles. Normalize inputs into a unified availability model that computes overlap windows by cohort and the entire group. Respect personal preferences (e.g., earliest/latest acceptable times) and enforce global policies (e.g., maximum after-hours instances per person). Persist computed windows for fast lookup by the rotation engine and flag participants with insufficient availability for admin resolution.

Acceptance Criteria
Auto-Detect Participant Time Zones
Given a connected calendar has a defined time zone and the user profile also has a time zone When aggregation runs Then the participant's primary time zone is set to the calendar's time zone in IANA format Given no calendar time zone exists but a profile time zone does When aggregation runs Then the participant's primary time zone is set to the profile's time zone in IANA format Given neither calendar nor profile provides a time zone When aggregation runs Then the participant is flagged "insufficient data: time zone" and excluded from window computation Given a date range spans a daylight saving transition for the participant's time zone When availability is computed Then UTC offsets for all intervals reflect IANA rules for the specific dates
Working Hours Inference and User Overrides
Given a connected calendar defines working hours and the user profile also defines default working hours When aggregation runs Then the participant's default working hours are taken from the calendar; if absent, from the profile Given a participant has personal earliest and latest acceptable times When availability is computed Then all availability intervals are clipped to those personal bounds Given no working hours are available from calendar or profile When aggregation runs Then the participant is flagged "insufficient data: working hours" and excluded from window computation until resolved Given a participant updates earliest/latest acceptable times When the next aggregation run completes Then the unified availability reflects the new bounds used by the rotation engine
Regional Holidays Ingestion and Exclusion
Given a participant's region is known When aggregation runs Then public holidays for that region within the rotation period are excluded from the participant's default working hours Given a regional holiday is observed on an alternate weekday When aggregation runs Then the observed date is excluded from availability computations Given multiple participants across different regions When cohort and group overlaps are computed Then each participant's regional holidays are excluded individually before intersection
Unified Availability Model and Overlap Computation
Given N participants assigned to cohorts When aggregation completes Then per-participant availability intervals are persisted in UTC and local time, non-overlapping and chronologically ordered Given per-participant availability exists When cohort overlap windows are computed Then the result is the intersection of all members' intervals, expressed in UTC, with a minimum granularity of 15 minutes Given all cohorts' participants When whole-group overlap windows are computed Then the result is the intersection across all participants, expressed in UTC, with a minimum granularity of 15 minutes Given any availability or overlap computation When results are persisted Then intervals contain no overlaps, are sorted, and include metadata identifying cohort_id and the participants contributing to each interval
Global Policy Enforcement: Max After-Hours Instances
Given a global policy max_after_hours_per_person = X for the rotation period When availability windows are computed Then any intervals outside a participant's default working hours are tagged as after-hours Given after-hours tags and the policy limit X When availability is finalized Then intervals that would cause a participant to exceed X after-hours instances are excluded from that participant's availability Given policy enforcement removes all after-hours intervals for a participant When overlap windows are computed Then that participant contributes only in-hours intervals; if no overlap remains, the participant is flagged "insufficient availability: policy limit"
Persistence, Caching, and Fast Lookup for Rotation Engine
Given computed availability for a rotation period When persisted Then it is stored with a version and last_updated timestamp and is retrievable by participant, cohort, and date range Given a lookup request from the rotation engine for a cohort and date range When served from the cache Then P95 latency is <= 100 ms and results match the latest persisted version Given source calendar or preference changes are detected When the next aggregation run completes Then persisted windows are updated and cache entries for affected participants and cohorts are invalidated
Insufficient Availability Detection and Admin Flagging
Given a participant has zero overlap with the whole group across the rotation period When aggregation completes Then the participant is flagged "insufficient availability: no group overlap" with reason codes Given a participant is missing required inputs such as time zone or working hours When aggregation runs Then the participant is flagged with the specific missing fields and excluded from overlap computations Given any participant is flagged for insufficient availability When admin-facing views or APIs are queried Then the participant appears in a Needs Resolution list with associated reason codes
Daylight Savings Transition Handling
"As a teammate in a region with DST, I want meetings to adjust seamlessly around clock changes so that I’m not penalized with after-hours times."
Description

Model daylight saving transitions per locale and apply them to the rotation so that each meeting instance remains within attendees’ stated work windows across clock changes. Detect upcoming DST boundaries and rebalance rotation slots in affected weeks to preserve fairness across regions. Provide proactive warnings when a transition would push an instance outside acceptable hours and auto-adjust or request approval based on policy settings.

Acceptance Criteria
Prebuild DST-Aware Quarter Rotation
Given a connected team with attendees across multiple timezones and each attendee has stated work windows for the next quarter And the system has the latest IANA time zone data loaded When the FairStart rotation is generated for the next 13 weeks Then the system identifies DST transition boundaries per attendee locale within the quarter And applies the correct post-transition UTC offsets to all impacted instances And ensures no generated instance start time is outside any attendee's stated work window.
Maintain In-Window Meetings Across DST Week
Given an existing weekly series with attendees in locales with differing DST start dates When a DST shift occurs for any attendee during a series week Then the system adjusts that week's meeting instance time so that it is within every attendee's stated work window And the series cadence (weekday and frequency) remains unchanged And no attendee is scheduled outside their stated work window.
Proactive DST Warnings and Policy-Based Adjustments
Given a scheduled instance that, under current DST rules, would fall outside at least one attendee's work window And a policy with notificationLeadTime and autoAdjust flag is configured When the system detects the upcoming DST boundary at or before notificationLeadTime Then if autoAdjust is true, the instance is auto-shifted to the nearest time slot that satisfies all attendees' work windows and fairness constraints, and updated invitations are sent And if autoAdjust is false, a warning is sent to the organizer with at least two compliant time options and an approval link, and the calendar is not changed until approval And all warnings are issued at least notificationLeadTime before the transition And the action (auto-adjust or pending approval) is recorded in the audit log.
Fairness Rebalance for DST-Affected Weeks
Given a quarter rotation with fairness dimensions region and role When DST causes one or more instances to be adjusted in affected weeks Then the system rebalances assignment of early/late burdens across regions and roles so that the count per region and per role differs by no more than 1 from the target distribution for the quarter And no individual's early/late burden count exceeds the team average by more than 1 And rebalancing does not create any instance outside any attendee's work window And early/late burden is defined as instances scheduled in the first or last hour of an attendee's work window And a before/after fairness summary is available to the organizer.
Locale-Specific DST Modeling Accuracy
Given attendees with locales including America/Los_Angeles, Europe/Berlin, Australia/Sydney, Asia/Kolkata, and America/Sao_Paulo When the system computes DST transitions for the next quarter Then the computed transition dates and offsets match the IANA tz database for those locales And for non-DST locales (e.g., Asia/Kolkata) no transitions are applied And if the underlying tz database is updated during the quarter, the system refreshes rules and recomputes impacted instances within 24 hours and notifies the organizer of changes.
Calendar Updates Preserve Series Integrity
Given an instance is auto-adjusted due to a DST transition When update notifications are sent to participants Then the iCalendar event maintains the same UID and increments the SEQUENCE per RFC 5545 And participants do not receive duplicate instances And existing attendee response statuses are preserved And external calendars reflect the updated start time within 15 minutes.
Conflict Exceptions & Blackout Integration
"As a product manager, I want to mark travel weeks as unavailable so that the rotation skips me without burdening the same region repeatedly."
Description

Support one-off exceptions (vacations, travel, company events) via user-declared blackouts and automatic ingestion of OOO calendar signals. When conflicts occur, recalculate only the impacted instances while maintaining overall fairness targets for the quarter and minimizing churn. Provide guardrails for how often participants can be swapped within a window, maintain an audit of changes and their rationale, and notify stakeholders of adjustments before invites are updated.

Acceptance Criteria
User-Declared Blackout Exception Handling
Given a participant creates a blackout covering specific dates/times in their local timezone for a meeting series within the next quarter When the system processes the blackout Then only the instances overlapping the blackout are marked as conflicted and recalculated And the recalculation preserves quarterly fairness with: per-participant assignment delta ≤ 1 instance and time-zone burden variance ≤ 10% And ≤ 2 participants are changed per affected instance And a preview of the proposed changes is generated without sending calendar updates
OOO Calendar Signal Ingestion to Blackouts
Given a connected calendar contains Out of office events for a participant And webhooks are enabled When an OOO event is created, updated, or deleted Then the system creates/updates/deletes corresponding blackout windows within ≤ 5 minutes (webhook) or ≤ 60 minutes (scheduled sync) And overlapping or duplicate blackouts are merged without double-counting And impacted instances are recalculated per fairness and minimal-change rules And an audit entry records source "Calendar OOO" with external event ID and change summary
DST-Aware Rotation Preservation
Given an upcoming daylight savings time change affects any participant’s locale during the quarter When the DST change would shift a recurring meeting outside any participant’s configured work window Then the system adjusts only the impacted post-DST instances to keep them within each participant’s work window And total changed instances due to DST ≤ 25% of the affected period’s instances And quarterly fairness metrics remain within the thresholds defined in this requirement And stakeholders are notified ≥ 7 days before changes are applied
Swap Frequency Guardrails
Given a participant belongs to a meeting series with conflict handling enabled When recalculations propose swaps within a rolling 30-day window Then no participant is auto-swapped more than 2 times per series per 30 days And exceeding the limit requires an explicit admin override with a required reason And attempts without override are blocked and logged without applying changes
Churn Minimization During Recalculation
Given conflicts are detected for N instances in the quarter When recomputing assignments Then the algorithm minimizes the number of changes subject to fairness constraints by preferring: preserving existing assignments, intra-role swaps, and intra-region swaps And global churn = changed_instances / total_instances ≤ 10% for the series in the quarter And per-participant churn ≤ 3 changed instances per quarter And recomputation completes within ≤ 30 seconds for up to 200 participants and 50 meeting series
Auditability and Pre-Update Notifications
Given any adjustment is proposed by blackout, OOO, DST, or admin action When a changeset is generated Then an immutable audit record is created capturing: timestamp, initiator, source, affected instances, fairness metrics before/after, churn metrics, and rationale And organizers and affected participants receive a pre-change summary with a 12-hour review window before invites are updated And if no action is taken within the window, the changes auto-apply and updated invites are sent And if the organizer rejects the changes, no calendar updates are sent and the audit logs the decision with reason
Rotation Preview & Team Acknowledgment
"As a team, I want to preview and confirm the planned rotation so that issues are caught before invites go out."
Description

Offer a review experience showing the next quarter’s rotation by person and by region, with local times, DST notes, and a fairness summary (early/late counts, after-hours reduction). Enable one-click acknowledgment for participants and an issue-flagging flow for exceptions. Track acceptance status and prevent invite dispatch until minimum quorum is met or an admin overrides. Persist a signed-off version of the plan to support later comparisons when changes occur.

Acceptance Criteria
Preview by Person: Local Times with DST Notes
Given a team with a computed next-quarter FairStart rotation When a user opens Rotation Preview and selects the By Person tab Then each participant’s occurrences for the next quarter are listed in their local timezone And any occurrence affected by a DST change displays a DST note explaining the shift And no occurrence displays outside the participant’s configured work window And the preview covers the entire next quarter without gaps or duplicates
Preview by Region: Fairness Summary Visible
Given the rotation is computed When a user opens Rotation Preview and selects the By Region tab Then participants are grouped under their region with their local meeting times And the fairness summary shows, per participant, early counts and late counts for the quarter And the fairness summary shows a team-level after-hours reduction percentage for the quarter And all displayed counts and percentages match the backend calculation within rounding rules
Participant One-Click Acknowledgment
Given a participant views the rotation preview for the current draft version When the participant clicks Acknowledge or uses a one-click acknowledge link Then the system records an acknowledgment with participant ID, version ID, timestamp, and source And the participant’s status updates to Acknowledged in the UI within 3 seconds And repeated clicks do not create duplicate acknowledgments (idempotent)
Flag Issue for Exception
Given a participant identifies a conflict in their rotation When the participant clicks Flag an Issue Then the system requires a reason selection (Schedule Conflict, PTO, Accessibility, Role Coverage, Other) and accepts an optional note And the flagged occurrence(s) are recorded with participant ID, version ID, reason, and timestamp And the participant’s status updates to Flagged And admins are notified immediately of the new flag
Quorum Gate for Invite Dispatch with Admin Override
Given a minimum quorum percentage is configured for the team (default 80%) When fewer than the required percentage of participants have acknowledged the current draft Then the Dispatch Invites action is disabled and a tooltip explains the unmet quorum When the required quorum is reached Then the Dispatch Invites action becomes enabled When an admin chooses Override and Dispatch before quorum Then a mandatory override reason is captured and audit-logged and invites are dispatched
Persist Signed-Off Version of Rotation Plan
Given quorum is met or an admin override is invoked When the organizer dispatches invites Then the system creates and persists a signed-off version containing schedule entries, fairness summary, DST notes, and participant acknowledgment statuses And the signed-off version is immutable, identifiable by a version ID and checksum And the signed-off version is retrievable from the plan history
Compare Changes Between Signed-Off and Current Draft
Given at least one signed-off version exists and a new draft rotation is generated When a user opens Compare to Signed-Off Then differences in meeting times, affected participants, and region allocations are highlighted And fairness metric deltas (early counts, late counts, after-hours reduction) are displayed for the draft vs the signed-off version And acknowledgments from the signed-off version do not carry over; draft acknowledgment statuses start as Pending And invites for the draft cannot be dispatched until the draft meets its quorum or an admin override is used
Invite Dispatch & Calendar Sync
"As a meeting organizer, I want invites sent automatically for the whole quarter so that scheduling time is minimized and updates stay in sync."
Description

After acknowledgment, send one-click invites for all approved instances via Google Workspace and Microsoft 365 integrations, with ICS fallback. Keep invites in sync with later adjustments by emitting incremental updates and minimizing calendar churn (no unnecessary cancellations). Include clear locale-aware times in the invite body, a link to the rotation preview, and per-instance reschedule controls governed by fairness policies.

Acceptance Criteria
Initial Dispatch Across Providers with ICS Fallback
Given a rotation has been acknowledged and instances are marked Approved And organizer accounts are connected to Google Workspace or Microsoft 365 And attendee lists and time windows are finalized When the user clicks Send Invites once Then events are created for all approved instances via the connected provider APIs within 60 seconds And attendees without provider support receive an email with an .ics attachment for each instance And each event contains the correct title, description, location/meeting link, attendees, and timezone And no invites are sent for unapproved or draft instances And a delivery summary is logged showing success/failure per instance and per attendee
Incremental Updates Without Calendar Churn
Given invites have been dispatched for a rotation And a single instance is modified (time, attendees, or description) after dispatch When the change is saved Then only that instance is updated using a provider update (not cancel/recreate) And the event UID/ID remains stable for the series and the instance exception And attendees receive a single update notification (not a cancel + new invite) And no additional series-wide changes occur unless explicitly edited And audit logs show 1 update action and 0 cancels for the instance And if an instance is deleted, only that instance sends a cancellation
Locale-Aware Time Formatting in Invite Body
Given attendees have known locales and timezones When invites or updates are sent Then the invite body includes the meeting time in the rotation’s primary timezone and in each attendee’s local time And date/time formats match each attendee’s locale conventions (e.g., 24h vs 12h, day/month order) And the timezone abbreviation or offset is shown unambiguously for the scheduled date And acceptance tests verify formatting for at least en-US, en-GB, de-DE, fr-FR, and ja-JP
Rotation Preview Link Included and Correct
Given invites are generated for a rotation instance When the invite is delivered Then the body contains a single link to the rotation preview for that rotation And following the link after authentication opens the rotation with the correct instance highlighted And the link includes no sensitive personal data in the URL And the link remains valid for at least the quarter duration unless the rotation is deleted
Per-Instance Reschedule Controls Enforce Fairness
Given an instance has been dispatched And a user opens per-instance reschedule controls When a proposed time violates fairness policies (e.g., exceeds after-hours quota for any region/role) Then the system blocks the change and presents the specific fairness rule violated And when a proposed time complies with fairness policies Then the system permits the change and sends an incremental update to attendees only for that instance And fairness metrics are recalculated for affected future instances without altering unchanged instances
Daylight Savings and Timezone Shift Handling
Given a recurring rotation spans one or more daylight savings transitions across attendee regions When invites are dispatched and later viewed around the transition dates Then each instance reflects the intended local working-hour slot for each region post-transition And no instance drifts outside configured work windows due to DST changes And provider calendars show the correct local wall time on the event detail page for the event date And automated tests cover at least two DST-forward and two DST-back scenarios across regions (e.g., US, EU, APAC)
Idempotent Dispatch and Retry Safety
Given the Send Invites action is triggered And a network error causes the client to retry the request one or more times within 10 minutes When the server processes duplicate dispatch requests Then no duplicate events are created in any attendee calendar And the same event UIDs/IDs are reused for already-created instances And the delivery summary clearly marks retried operations as deduplicated And monitoring shows at-least-once processing with exactly-once effects for each instance

RoleSmart Templates

Detects your use case (standup, planning, retro, 1:1, release sync) and applies best‑practice defaults for duration, cadence, and attendees. Start from proven presets so you can schedule confidently in seconds.

Requirements

Automatic Use-Case Detection
"As a remote team lead, I want TimeTether to recognize the meeting type from my title and notes so that I can start from the right defaults without manual setup."
Description

Identify the meeting type (standup, planning, retro, 1:1, release sync) from user inputs such as meeting title, initial agenda text, and recent scheduling patterns, then surface the best matching template with a confidence score. Provide a lightweight confirmation UI and a manual override to select a different template. Support rule-based keyword matching with an extensible dictionary and optional ML model for improved accuracy over time, while respecting privacy by processing client-side where feasible and not persisting sensitive text beyond session without consent. Integrate with TimeTether’s fairness engine and timezone model to immediately map detected use cases to appropriate duration and cadence defaults. Log anonymized detection outcomes for analytics and continuous improvement. Ensure detection performance under 150ms to keep the flow instant.

Acceptance Criteria
Use-Case Detection from Inputs
Given a meeting draft with title "Team Standup" and agenda "yesterday / today / blockers" When automatic detection runs Then predicted_use_case = "standup" AND confidence >= 0.80 AND source IN {"rule","ml","mixed"} And confidence is returned as a numeric value in [0,1] with two decimal places Given a meeting draft with title "Sprint Planning — Q3" When automatic detection runs Then predicted_use_case = "planning" AND confidence >= 0.70 Given a meeting draft with title "Retro: Sprint 42" When automatic detection runs Then predicted_use_case = "retro" AND confidence >= 0.70 Given a meeting draft with title "1:1 with Alex" When automatic detection runs Then predicted_use_case = "1:1" AND confidence >= 0.85 Given a meeting draft with title "Release Sync v2.1" When automatic detection runs Then predicted_use_case = "release sync" AND confidence >= 0.70 Rule: predicted_use_case must be one of {standup, planning, retro, 1:1, release sync} Rule: Detection must succeed even if agenda text is empty (title-only)
Confidence Threshold and Confirmation UI
Rule: If confidence >= 0.75, auto-apply the top template and display an inline confirmation chip showing template name and a "Change" action; single Enter/Return confirms Rule: If 0.50 <= confidence < 0.75, present a suggestion sheet with the top 3 templates sorted by confidence; no template is pre-applied until the user confirms Rule: If confidence < 0.50, open in "Select template" state with no suggestions pre-applied; user must pick manually Rule: Confidence label shown as High (>=0.75), Medium (0.50–0.74), Low (<0.50) Given the user clicks Confirm in the High or Medium state When confirmation is accepted Then the selected template is set and the sheet closes within 200ms Rule: The final selected template is visibly reflected in the scheduling form (template name and defaults)
Manual Override Selection
Given a suggested template is shown When the user opens the template picker and selects a different template Then the newly selected template replaces the suggestion immediately and the UI label shows "Manual override" And any subsequent auto-detection updates do not overwrite the manual selection during the current scheduling flow Rule: Provide a "Revert to Suggested" action that restores the last auto-detected template if available Rule: The manual override persists through to invite creation and is recorded for analytics as override_flag = true
Defaults Mapping via Fairness and Timezone Models
Given a template is selected (auto or manual) When defaults are applied Then duration and cadence fields populate according to the template’s best-practice defaults adjusted by TimeTether’s fairness engine and timezone model Rule: Group meetings (standup, planning, retro, release sync) enable fairness rotation by default; 1:1 does not enable rotation Rule: If any attendee would be outside their work window for the next occurrence, the rotation schedule distributes after-hours burden such that no attendee has >50% of the next 4 occurrences after-hours Rule: The populated defaults render within 100ms of template selection and are editable by the user Rule: If a team policy overrides a default (e.g., duration), the policy value takes precedence and is labeled as "Policy"
Privacy: Client-Side Processing and Ephemeral Text
Rule: Detection runs entirely client-side; no network requests transmit raw title or agenda for detection purposes (verified by network logs) Rule: Sensitive text (title, agenda) is not persisted beyond the current session without explicit user consent; local storage contains no raw title/agenda keys Rule: If an optional ML model is used, it loads and executes locally; only model and rule version identifiers may be sent for telemetry Rule: A privacy notice is shown explaining local processing and optional analytics; users can view and change consent at any time
Performance SLA: Sub-150ms Detection
Rule: Detection latency p95 <= 150ms and p99 <= 250ms on reference client devices for titles <= 120 chars and agendas <= 500 chars Rule: First-load (cold) model/dictionary initialization p95 <= 400ms per session; subsequent detections meet the main SLA Rule: The UI remains non-blocking; if detection exceeds 250ms, the template picker is usable and the detected suggestion appears asynchronously when ready Rule: Each detection emits a local latency metric for observability (without raw text)
Anonymized Outcome Logging and Opt-In
Rule: When org-level analytics opt-in is enabled, an event is logged containing: detection_id, timestamp, org_hash, predicted_template_id, confidence_bucket ∈ {High, Medium, Low}, source ∈ {rule, ml, mixed}, override_flag (bool), latency_ms; no raw title/agenda or personal identifiers are included Rule: When opt-in is disabled, no detection events are transmitted or stored Rule: Events are retried on transient failures and sampled at 100% up to 10k/day/org, thereafter at 50% Rule: Logged payloads pass automated PII scanning with zero raw name/email matches
Best-Practice Template Catalog
"As a PM, I want access to proven meeting templates with sensible defaults so that I can schedule effective ceremonies quickly and consistently across the team."
Description

Provide a curated, versioned catalog of meeting templates for common product and engineering ceremonies, each with defaults for duration, cadence, attendee roles, agenda outline, preparation reminders, and conferencing options. Templates should be region- and time zone–aware, aligning default work windows with participant locales and TimeTether’s fairness rotation rules. Allow organization-level overrides to adapt templates to internal norms, with inheritance from global presets and change logs for auditability. Include semantic metadata (e.g., ceremony type, team size, sprint length) to enable precise matching by the detection engine and future automation. Ensure backward compatibility so existing events continue using prior template versions until users opt in to updates.

Acceptance Criteria
Versioned Templates and Backward Compatibility
- Given an existing event instantiated from template X version 1.2.0, When a new version 1.3.0 of template X is released, Then the existing event continues using version 1.2.0 defaults until the owner explicitly opts in to the new version. - Given a user opens the event's template settings, When they click "Update to latest", Then the event's defaults are updated to 1.3.0 and a change log entry is recorded with actor, timestamp, old_version, new_version, and modified fields. - Given an API client requests template X without specifying a version, When the latest is 1.3.0, Then the API returns version 1.3.0 and includes latest_version, previous_versions[], and a semver string. - Given an API client requests template X at version 1.2.0, When 1.2.0 is supported, Then the API returns the 1.2.0 payload and status "supported"; When 1.2.0 is deprecated, Then status "deprecated" is returned but the payload remains accessible. - Given a template change introduces incompatible field changes, When released as a new major version (2.0.0), Then existing events remain pinned to prior major versions until opt-in is confirmed.
Template Field Completeness and Defaults
- Given any template in the catalog, Then it shall define non-null defaults for duration (minutes), cadence (cron/ISO cadence), attendee roles (required/optional), agenda outline (ordered items), preparation reminders (timing and content), and conferencing options (provider and creation mode). - Given a user creates a meeting from a template, When the template is applied, Then the meeting draft is prepopulated with those default values and passes schema validation without warnings. - Given attendee roles are specified, When a team with mapped roles is selected, Then required roles are auto-populated from directory groups and optional roles are suggested. - Given agenda outline items exist, When the meeting length is changed by the user, Then time allocations adjust proportionally and remain within ±5% of total duration.
Region- and Time Zone–Aware Work Windows with Fairness Rotation
- Given participants span at least two time zones, When default time suggestions are generated for the template, Then every suggested time falls within each participant's configured work window; If no such time exists, Then suggestions minimize total after-hours minutes and apply fairness rotation across occurrences. - Given a recurring series generated from the template, When fairness rotation is enabled, Then the meeting start time rotates such that no participant is outside work hours more than any other by more than one occurrence over a 12-week window. - Given locale-specific holidays are present, When generating suggestions, Then suggestions avoid public holidays for participants' locales unless the organizer overrides with "Allow holiday scheduling".
Organization-Level Overrides with Inheritance and Audit Logs
- Given a global template value exists and an org override is not set, Then the resolved value for that org equals the global value. - Given an org admin sets an override for duration and agenda, When a user in that org applies the template, Then the resolved duration and agenda equal the override while other fields inherit from global. - Given an org override is removed, When the template is next resolved, Then the value reverts to the current global default. - Given any override change is saved, Then a change log entry is created with org_id, template_id, field, old_value, new_value, actor, timestamp, and reason (optional) and is visible in audit reports. - Given a field is marked as "locked by org policy", When a user attempts to modify it during scheduling, Then the UI prevents the change and displays the policy message.
Semantic Metadata for Detection and Filtering
- Given a template in the catalog, Then it includes metadata for ceremony_type, team_size_range, sprint_length_weeks, remote_mode, required_roles, and tags. - Given the detection engine receives context {ceremony_type: 'standup', team_size: 8, sprint_length_weeks: 2, remote_mode: 'remote'}, When matching, Then the top-ranked template has ceremony_type='standup' and supports team_size 8 and sprint_length 2 with required roles satisfied. - Given an API consumer queries /templates?ceremony_type=retro&team_size=12, When results are returned, Then all items match the filters and are sorted by score descending; response includes pagination and total_count.
Conferencing Options Integration and Fallbacks
- Given a template specifies conferencing_options {provider: 'Zoom', creation_mode: 'auto'}, When a meeting is created, Then a Zoom meeting is provisioned and the join link is attached to the invite within 5 seconds at the 95th percentile. - Given the specified provider is unavailable, When scheduling proceeds, Then the system falls back to the organization default provider and records a warning in the event log. - Given a template specifies creation_mode 'attach-existing', When the organizer supplies a conferencing link, Then the system validates the URL against the provider schema and prevents saving if invalid.
One-Click Apply & Preview
"As an engineering manager, I want to apply a preset in one click and preview the changes so that I can schedule confidently without re-entering details."
Description

Enable users to apply a detected or chosen template to a draft meeting with a single action and see a clear preview of all changes before saving: duration, cadence, attendees by role, agenda outline, and time window constraints. Highlight field-level diffs, provide undo/redo, and allow partial application (e.g., apply duration and cadence but not attendees). Validate that applied defaults comply with team policies and fairness constraints, surfacing conflicts and suggested fixes inline. Ensure the interaction is keyboard accessible and performs within 100ms for perceived instant feedback.

Acceptance Criteria
One-Click Apply Preview with Field-Level Diffs
Given a draft meeting and a detected or chosen template When the user activates "Apply Template" via mouse click or pressing Enter Then a preview opens without persisting changes and focus moves to the preview header And the preview shows updated values for duration, cadence, attendees by role, agenda outline, and time window constraints And each changed field is visually highlighted with a before → after diff; unchanged fields have no highlight And Save and Cancel controls are visible and enabled And selecting Save commits all previewed changes; selecting Cancel discards all previewed changes and returns to the draft And no changes are saved to the draft until Save is confirmed
Partial Application of Template Fields
Given a draft meeting with the template preview open When the user selects only Duration and Cadence and deselects Attendees, Agenda, and Time Window in the apply options Then the preview updates only Duration and Cadence and leaves Attendees, Agenda, and Time Window unchanged And the diff highlights appear only for the selected fields And upon Save, only the selected fields are persisted to the draft And toggling selections updates the preview immediately without saving
Inline Policy & Fairness Validation with Suggested Fixes
Given template defaults that violate a team policy or fairness constraint (e.g., outside core hours, uneven rotation) When the preview is generated Then blocking conflicts are surfaced inline adjacent to the offending fields with a clear message and severity And a suggested fix action is provided for each conflict (e.g., "Adjust time window to 9:00–17:00", "Rotate facilitator") And selecting a suggested fix updates the preview and re-runs validation And Save is disabled while blocking conflicts exist unless the user provides a required override reason per policy And non-blocking warnings allow Save and are logged
Keyboard-Only Interaction and Shortcuts
Given a keyboard-only user is focused on the draft meeting When they press Enter on "Apply Template" Then the preview opens and focus moves to the preview header And all interactive controls in the preview (field toggles, suggested fix buttons, Save, Cancel) are reachable by Tab/Shift+Tab and operable with Enter/Space And pressing Esc closes the preview and returns focus to the "Apply Template" control And Ctrl+Z performs Undo and Ctrl+Y (or Ctrl+Shift+Z on macOS) performs Redo within the preview And all focused elements show a visible focus indicator and there are no keyboard traps
Undo/Redo in Preview
Given a user has applied a template and made additional changes (e.g., toggled fields, applied suggested fixes) in the preview When the user invokes Undo Then the most recent change is reverted in the preview and diff highlights update accordingly When the user invokes Redo Then the reverted change is re-applied and diff highlights update accordingly And the Undo/Redo history supports at least 10 consecutive steps per draft session And closing the preview clears the Undo/Redo history; reopening starts a fresh history
Performance: Apply & Preview Within 100ms
Given the standard test environment and representative team data When the user triggers "Apply Template" Then time from activation to fully rendered preview with diff highlights and initial validations completes in ≤ 100 ms at the 95th percentile And applying a single suggested fix or toggling a field selection updates the preview in ≤ 100 ms at the 95th percentile And performance is measured via instrumentation and logged with operation name, start/end timestamps, and outcome
Role-Based Attendees Resolution and Dedupe
Given a template that specifies attendees by role (e.g., Product Lead, Eng Manager, QA) When the template is applied in preview Then attendees are resolved from the team directory by role and added without duplicating existing invitees And external invitees not covered by the roles remain unchanged unless explicitly included by the template And unresolved roles surface an inline warning with a suggested fix to map the role or skip attendees And saving persists only the attendee changes shown in the preview (if Attendees is selected)
Role-Based Attendee Suggestions
"As a scheduler, I want the system to suggest the right people by role so that I include the necessary stakeholders without hunting through the directory."
Description

Automatically suggest attendees based on template-defined roles (e.g., product lead, EM, QA representative) by mapping roles to people using the org directory (Google Workspace, Microsoft 365) and historical attendance patterns. Offer ranked suggestions with reasoning, avoid auto-adding external or sensitive groups without explicit consent, and respect team access controls. Detect gaps (e.g., no QA assigned) and prompt the user to pick from eligible candidates. Continuously learn from accepted/rejected suggestions to improve relevance. Integrate with TimeTether’s conflict resolution to flag overloaded individuals and propose alternates.

Acceptance Criteria
Template-Driven Role Mapping to People
Given the organizer selects a RoleSmart template containing defined roles and the org directory (Google Workspace or Microsoft 365) is connected When the attendee suggestions panel loads Then for each defined role the system suggests 1–3 eligible people mapped from the directory And suggestions exclude deactivated accounts, external domains, and users outside the org And each suggestion displays full name, mapped role label, team, and a primary reason badge And the suggestions render within 2 seconds for an org with up to 2,000 users
Ranked Suggestions with Transparent Reasoning
Given multiple eligible people exist for a template role When suggestions are displayed Then candidates are sorted by a numeric confidence score (0–100) And the top candidate has the highest score for that role And each suggestion shows at least one reason (e.g., "Current EM of Team A", "Attended 6/8 recent sessions") And clicking "Why suggested" reveals a score breakdown by signal category
Respect Access Controls and Sensitive Groups
Given the organizer’s access is limited to specific teams and some groups are marked sensitive (e.g., Executives, Legal, HR) or External When generating attendee suggestions Then only users within the organizer’s permitted scope appear And users in sensitive or external groups are never auto-added And attempting to add a sensitive or external user requires explicit organizer confirmation via a modal checkbox "I have consent" before adding And all such confirmations are audit-logged with timestamp, organizer, and user added
Gap Detection and Prompt for Missing Roles
Given a template role has no eligible candidate in the directory or historical data When suggestions are generated Then the role is marked "Unfilled" And the system prompts the organizer with a filtered people picker pre-filtered by title/team keywords for the role And the organizer cannot finalize the meeting until all required roles are filled or explicitly skipped with confirmation And the skip action is recorded in the audit log
Learning from Accepted/Rejected Suggestions
Given the organizer accepts or rejects suggested attendees When the organizer confirms attendees Then each accept/reject action is stored with role, candidate, timestamp, and outcome And rejecting the same candidate for the same role 3 times by the same organizer decreases that candidate’s score for that role by at least 30% for that organizer’s team And accepting the same candidate for the same role 3 times increases that candidate’s score by at least 20% And the updated scores affect ranking on the next suggestion generation for that organizer
Conflict Load and Alternate Proposals
Given a suggested attendee has a time conflict at the proposed meeting time or exceeds the weekly meeting load threshold configured in TimeTether When suggestions are displayed Then the attendee is flagged with a "Conflict" or "Overloaded" indicator And at least one alternate from the same role/team and compatible timezone is proposed if such an alternate exists And clicking the indicator shows conflict time blocks or load metrics And if the organizer selects the flagged person, a confirmation is required and recorded
Resilience and Fallback When Directory or Signals Unavailable
Given the org directory or historical signals service is unavailable or times out When generating suggestions Then the system displays a non-blocking inline error and falls back to a manual people picker And no external or sensitive users are auto-added in fallback And the system retries in the background up to 2 times within 60 seconds And failures are captured in telemetry and visible in admin diagnostics
Custom Template Builder
"As a team lead, I want to tailor and save our ceremony presets so that our scheduling reflects our team’s norms without rework each time."
Description

Allow users to create, clone, and edit templates by adjusting defaults for duration, cadence, attendee roles, agenda outline, reminders, conferencing, and time-window constraints. Support saving at personal, team, and org scopes with permissions and sharing controls. Provide validation against fairness and work-hour rules, plus guidance badges that indicate alignment with best practices. Include import/export for portability and versioning to track changes over time. Ensure templates are discoverable by the detection engine via tag and metadata configuration.

Acceptance Criteria
Create New Template with Defaults
Given I am on the Custom Template Builder with a valid account When I enter a unique template name and configure duration (5–180 minutes), cadence (daily/weekly/bi-weekly/monthly or custom CRON), attendee roles (≥1 role), agenda outline (≤20 items), reminders (0–3 with offsets), conferencing (URL or provider), and time-window constraints (per role, in their timezones) And I leave any optional field blank And I click Save to Personal scope Then the template is created within 2 seconds with a new ID And all provided values persist exactly as entered And unspecified optional fields are set to documented defaults And the template appears in My Templates list immediately And required-field errors are shown inline and block Save until resolved
Clone Template Across Scopes
Given a template exists in Team scope When I choose Clone and select Personal scope And I set a new unique name Then a new template is created within 2 seconds with identical settings except scope and name And the new template has a distinct ID and version = 1 And permissions reflect the target scope And the source template remains unchanged
Edit Template and Versioning
Given a template at version 3 exists When I change duration and agenda and click Save with an optional change note Then version increments to 4 and the change note, editor, timestamp are recorded in history And a diff view lists changed fields and prior values And I can restore version 3, creating version 5 with version 3’s content And no data loss occurs for fields not edited
Scope Permissions and Sharing Controls
Given I own a Personal scope template When I share read-only with a teammate and keep edit permission to myself Then the teammate can view but cannot edit or delete the template And an unauthorized edit attempt returns 403 and shows a non-destructive error message And removing access immediately revokes visibility for that user And for Team scope templates, only designated editors can modify; org view is read-only unless in Editors group
Fairness and Work-Hour Validation with Guidance Badges
Given a template with recurring cadence and attendee roles that have declared work windows When I attempt to Save with time-window constraints that would schedule >25% of occurrences outside any role’s work window Then Save is blocked with an error listing offending roles and projected outside-hours percentages And when constraints comply (≤25% for all roles) and fairness rotation is enabled, Save succeeds And for recurring meetings with ≥2 roles across ≥2 timezones, fairness rotation must be On; otherwise Save is blocked with guidance to enable it And guidance badges compute within 1 second and display Best Practice when duration and cadence match recommended presets for the selected use-case tag; otherwise display Needs Attention with specific suggestions
Import and Export Templates
Given I have a valid template export JSON (schema version 1.0) When I import the file Then a dry-run summary displays count, name conflicts, and target scopes And on confirm, new IDs are assigned; name conflicts are resolved by suffixing “(imported)” unless I choose overwrite (allowed only for templates I own) And invalid files are rejected with a schema error listing up to the first 5 issues And exporting a template generates a JSON conforming to schema 1.0 including metadata, tags, and version history And import and export complete within 5 seconds per 1 MB file
Detection Engine Discoverability via Tags and Metadata
Given a template has use-case, team, and product-area tags and discoverability is On When the detection engine evaluates a scheduling scenario matching those tags Then the template appears in the top 3 recommendations if it holds a Best Practice badge; otherwise appears below best-practice templates but within the first page of results And turning discoverability Off removes the template from recommendations within 2 minutes And updating tags updates eligibility within 2 minutes And templates marked Private are never recommended to users without access
Admin Controls & Policy Enforcement
"As an org admin, I want to manage and enforce template standards so that teams schedule consistently and within company policies."
Description

Provide admin tools to set default templates by team, lock specific fields (e.g., minimum agenda, max duration), and schedule phased rollouts of updated templates. Offer audit trails of template changes and usage analytics (apply rate, overrides, meeting outcomes proxies) to evaluate effectiveness. Enforce compliance checks at apply time and during recurring updates, with policy explanations and remediation suggestions. Integrate with SSO/SCIM for role-based access and maintain GDPR-compliant data handling and retention settings.

Acceptance Criteria
Team-Based Default Template Assignment
Given an admin assigns a default template to Team Alpha in the Admin Console And an organization-level default template exists And a policy priority order is configured as: Team > Organization When a Team Alpha member invokes RoleSmart Templates to create a new meeting Then the Team Alpha default template auto-applies with its duration, cadence, and attendee presets And if the user belongs to multiple teams with defaults, the template from the highest-priority team in the policy order applies deterministically And if no team default exists, the organization default applies; if none, RoleSmart auto-detection is used And the change in defaults propagates to end users within 60 seconds of admin save And an event "template.default_applied" is recorded with userId, teamId(s), templateId, and timestamp
Field Locking and Enforcement
Given an admin locks fields Max Duration=45 minutes and Min Agenda Items=2 for Team Alpha When a non-admin user edits a meeting that uses a locked template via UI Then locked controls are disabled with a "Policy Locked" indicator and linked policy name And attempting to save with values that violate a locked field is blocked and shows an error listing each violated policy And the REST/GraphQL API returns HTTP 403 with errorCode=PolicyViolation and the list of violated fields and constraints And recurring series updates are validated on each instance creation/update And an admin can override by supplying a justification note; the override is recorded with actor, reason, and affected fields
Phased Rollout Scheduling and Governance
Given an admin schedules a phased rollout for Template T to Teams Alpha and Beta as 10% at 2025-09-05T09:00Z, 50% at 2025-09-07T09:00Z, 100% at 2025-09-10T09:00Z And the selection method is "random-stable by userId" with org-scoped salt When each phase start time occurs Then the specified percentage of eligible users receive Template T by default within 5 minutes of the phase start And users not in the current cohort continue receiving their prior defaults And pausing or resuming the rollout takes effect within 5 minutes and is reflected in the admin dashboard state And a dry-run shows expected cohort sizes per phase before activation And all cohort assignments and state changes are logged with phase, actor, and timestamp
Audit Trail Completeness and Immutability
Given any create, update, or delete action on templates, policies, or rollouts When the action is committed Then an audit entry is appended capturing actorId, actorRole, timestamp (UTC ISO8601), objectType, objectId, action, previousValues, newValues, scope (org/team), requestId, and sourceIp And audit entries are append-only; no UI or API exists to modify or delete entries before their retention period expires, and modification attempts return HTTP 405 And admins can filter audit entries by actor, team, date range, objectType, and export results to CSV And audit entries are ordered and continuity-checked (monotonic sequence per scope) so gaps are detectable
Privacy-Preserving Usage Analytics (GDPR)
Given analytics are enabled for RoleSmart Templates When admins view analytics for a selected time range, team(s), and template(s) Then the dashboard displays at minimum: applyRate, overrideRate, afterHoursMeetingRate delta, and policyViolationRate with definitions and calculation windows And metrics update at least every 15 minutes with 95th percentile freshness under 15 minutes And data is aggregated and pseudonymized; no free-text agenda content or attendee PII is displayed And retention is configurable (30–730 days); upon expiry, event-level identifiers are purged while precomputed aggregates remain And admins can export aggregated metrics as CSV; access to analytics requires Admin role and is audited And upon a user data erasure request, identifiable analytics events for that subject are deleted within 30 days without breaking aggregate counts beyond acceptable remainders
Compliance Checks with Explanations and Remediation
Given compliance policies define Max Duration=45 minutes and Min Agenda Items=2 And enforcement mode is set to Block When a user applies or updates a template that violates one or more policies Then a compliance panel lists each violated policy with human-readable explanation and policyId And a one-click "Fix and continue" action adjusts the meeting to the nearest compliant values And users can submit an exception request with justification; approval by an Admin applies only to the specified meeting/series and is logged And compliance evaluation runs in under 300 ms at p95 for apply-time checks and under 1 second for recurring series updates And notifications are sent to meeting organizers when a recurring series drifts out of compliance, with links to remediate
SSO/SCIM Role-Based Access Control
Given SSO is configured with an IdP and SCIM group-to-role mappings (Admin, Organizer, Viewer) When a user signs in via SSO Then their role is assigned based on SCIM group membership within 60 seconds of successful authentication And deprovisioned users lose access within 15 minutes of SCIM deactivation And only Admins can access the Admin Console and policy APIs; unauthorized attempts return HTTP 403 and are logged And team membership from SCIM determines which team defaults an organizer can view/edit; cross-team access is denied And authN/Z events (login, role change, forbidden) are recorded in the audit trail with userId and IdP subject

Instant Clash Check

Runs real‑time conflict scans across calendars, focus blocks, and blackout periods, then ranks the top conflict‑free options by fairness score. Pick a slot with one tap and avoid ping‑pong before it starts.

Requirements

Real-time Multi-Calendar Conflict Scan
"As a meeting organizer, I want Instant Clash Check to detect conflicts across all attendees’ calendars in real time so that I only see truly available slots."
Description

Continuously aggregate availability from all connected attendee calendars (busy/tentative/OoO), merging recurring events, last-minute updates, and shared resources to detect conflicts in real time. The scanner evaluates proposed ranges on demand and via event-driven triggers, returning only conflict-free windows. It integrates with TimeTether’s scheduling core to stream updated availability as calendars change, minimizing stale results and preventing ping‑pong.

Acceptance Criteria
Real-time update propagation on calendar change
Given multiple attendee calendars are connected and a conflict-free window list is displayed to the user When any attendee adds, updates, or cancels an event that overlaps a shown window Then the system receives the calendar change event and updates the availability within 3 seconds And the affected window is removed from the list if it is no longer conflict-free And a new conflict-free window is added if created by the change And the time-to-update at P95 is <= 5 seconds during normal load
On-demand range evaluation returns conflict-free windows only
Given the user requests availability for a specific date/time range and duration And all attendee calendars are reachable When the scan is executed Then the API responds with HTTP 200 within 2 seconds for ranges <= 14 days And every returned window falls entirely within the requested range and respects the requested duration And no returned window overlaps any attendee busy, tentative, focus block, blackout period, or OoO event And no returned window requires unavailable shared resources
Recurring events and exceptions are respected
Given an attendee has recurring events with exceptions within the requested range When the scan evaluates conflicts Then recurrence rules (RRULE, RDATE, EXDATE) are expanded correctly for the range And overridden instances replace their series defaults And canceled instances do not cause conflicts And all expanded instances are treated as conflicts if they overlap candidate windows
Focus blocks, blackout periods, and OoO honored as conflicts
Given organization-defined blackout periods and user-defined focus blocks are configured And attendees may have OoO events When the scan evaluates availability Then any time overlapping a blackout period, focus block, or OoO event is excluded from returned windows And 100% of overlapping intervals are excluded in the results
Shared resource availability enforced
Given a meeting requires one or more shared resources When the scan evaluates candidate windows Then any window overlapping a required resource's busy time is excluded And when all required resources are free, the window may be returned And resource availability checks complete within the same SLA as attendee checks (response time <= 2 seconds for ranges <= 14 days)
Availability streaming integration with scheduling core
Given the scheduling core is subscribed to availability updates for a session When calendar change events occur for any attendee or resource affecting the session's range Then the core receives a delta update message containing only changed windows And no window older than 10 seconds since the last relevant change event remains displayed as available And the stream connection recovers from transient disconnects by resyncing the latest availability within 10 seconds of reconnection
Unreachable calendars do not return false positives
Given one or more attendee calendars are temporarily unreachable or permissions are missing When the scan is executed Then the response marks the affected attendees as unknown And no windows that depend on unknown calendars are returned as conflict-free And an actionable error code is included per affected attendee
Focus & Blackout Period Ingestion
"As a team lead, I want focus blocks and blackout periods to be respected in conflict scans so that deep work and protected time aren’t interrupted."
Description

Import and honor user-level focus blocks, Do Not Disturb windows, team no‑meeting days, and company blackout periods as hard constraints in conflict detection. Support granular rules (e.g., weekly focus blocks, exception days) and per-user overrides. Integrates with TimeTether policy settings to ensure protected time is treated as non‑schedulable while still allowing explicit organizer overrides with consent.

Acceptance Criteria
User Focus & DND Treated as Non-Schedulable in Instant Clash Check
Given Participant A has a recurring focus block every Tue/Thu 14:00–16:00 and a daily DND 12:00–13:00 in their local timezone And Participant A is included as an attendee When Instant Clash Check runs to suggest available meeting slots Then no suggested slot overlaps any portion of Participant A’s focus or DND windows And partial overlaps are excluded And all evaluations use Participant A’s local timezone for protected windows
Team No-Meeting Days and Company Blackouts Enforced via Policy Settings
Given the organization policy defines Wednesday as a team no-meeting day for Team X and Dec 24–26 as a company blackout And the organizer is scheduling a meeting including Team X members When Instant Clash Check runs Then no suggested slots appear on any Wednesday for Team X members or on Dec 24–26 for any attendee And if the organizer attempts to schedule during these periods, the system requires explicit overrides per impacted user and only allows it if the "Allow blackout overrides" policy is enabled And if blackout overrides are disallowed by policy, scheduling during blackout periods is blocked with a policy error
Weekly Focus Blocks with Exception Days Respected
Given Participant B has a weekly focus block every Monday 09:00–11:00 with an exception on 2025-09-08 When Instant Clash Check scans for meetings on 2025-09-08 Then slots within 09:00–11:00 are considered schedulable for Participant B And when scanning any other Monday, slots within 09:00–11:00 are excluded for Participant B
Per-User Protected Time Override Requires Explicit Consent
Given a selected slot intersects protected time for Participants C and D When the organizer requests an override for that slot Then consent requests are sent to all impacted participants And the meeting is scheduled only after each impacted participant explicitly accepts the override And if any impacted participant declines, the meeting is not scheduled in that slot And an audit record captures who requested the override, who consented or declined, timestamps, and the overridden rule types
Real-Time Scan Performance with Protected Constraints
Given a meeting with up to 20 attendees, each with focus, DND, team no-meeting, and company blackout rules When Instant Clash Check runs Then the system returns a ranked list of conflict-free options within 2 seconds or a "no conflict-free options" result within 2 seconds And no returned option violates any attendee’s protected constraints
Timezone-Accurate Protected Time Resolution (including DST)
Given Participant E in PDT has DND 12:00–13:00 local and Participant F in CET has a focus block 09:00–11:00 local And the organizer is in GMT When Instant Clash Check scans across dates that include a daylight saving time change for any participant Then each participant’s protected windows are evaluated using their local timezone and DST rules And no suggested slot overlaps the converted protected windows for any participant
Fairness Score Ranking Engine
"As a distributed team member, I want candidate times ranked by fairness so that recurring meetings don’t consistently fall outside my work hours."
Description

Compute a fairness score for each conflict‑free candidate slot using factors such as cross‑timezone rotation, historical after‑hours burden, participant count impacted, recurrence effects, and equitable distribution over time. Rank and surface the top options with score breakdowns for transparency. Integrates with TimeTether’s fairness model to update rotation state when a slot is selected, ensuring long‑term balance across the series.

Acceptance Criteria
Ranking Conflict-Free Slots by Fairness Score
Given a request to score 10 conflict-free candidate slots for a meeting with 8 participants across 3 timezones and request.top_n=5 When the fairness engine computes scores Then every candidate includes a fairness_score numeric value between 0 and 100 with precision >= 2 decimal places And the candidates are sorted in strictly non-increasing order by fairness_score And the response returns exactly 5 top candidates And no candidate with a lower fairness_score appears above a higher one And each returned candidate includes slot_id, start_time, end_time, fairness_score
Score Breakdown Transparency
Given any computed fairness_score for a candidate slot When the result is returned Then the payload includes sub_scores for timezone_rotation, after_hours_burden, participant_impact, recurrence_effect, distribution_over_time each in 0–100 And the payload includes weights for each sub_score that sum to 1.0 ± 0.001 And the weighted sum of sub_scores equals the fairness_score within ±0.01 And a human_readable_explanation string <= 200 characters is present And a model_version identifier is present
Rotation State Update on Slot Selection
Given a user selects a ranked slot and confirms scheduling for a recurring series When the selection is processed Then the fairness model’s rotation state is updated persistently within 2 seconds And subsequent scoring requests for the same series reflect the new rotation state And an audit record with user_id, series_id, slot_id, timestamp, prior_state_hash, new_state_hash is stored And the operation is idempotent for repeated selection requests with the same idempotency_key And on persistence failure the transaction is rolled back and an error code rotation_update_failed is returned
Recurrence Effects Incorporated in Scoring
Given two candidate slots for a 12-occurrence weekly series with identical non-recurrence factors When recurrence_effect is evaluated Then the candidate that minimizes cumulative after-hours minutes across all participants receives a higher recurrence_effect sub_score And the difference in recurrence_effect sub_score is at least 5 points when cumulative after-hours minutes differ by >= 120 minutes And recurrence_effect sub_score is reported per occurrence and aggregated across the series
Participant Impact Penalty
Given two candidate slots where all sub-scores except participant_impact are equal When participant_impact counts impacted participants as after-hours or outside focus windows Then the slot impacting more participants receives a lower participant_impact sub_score And with model_config.participant_impact_penalty_per_person=3, a difference of 3 additional impacted participants reduces participant_impact sub_score by at least 9 points And participant_impact counts are included in the breakdown as impacted_count
Deterministic Tiebreaking and Stable Ranking
Given two or more candidates with fairness_score differences <= 0.001 When the ranking is generated Then ties are broken deterministically by lowest total after-hours minutes, then earliest median local time across participants, then ascending slot_id And repeated scoring with identical inputs produces identical ranking order and scores And a ranking_id and input_hash are returned for reproducibility
Real-Time Scoring Performance
Given up to 200 candidate slots and 15 participants across up to 8 timezones When the fairness engine computes scores Then p95 end-to-end scoring latency is <= 300 ms and p99 <= 500 ms measured server-side And CPU utilization for the scoring process remains below 70% at p95 under this load And memory allocation per request does not exceed 50 MB And performance metrics are emitted with request_id and model_version
One‑Tap Slot Selection & Invite Dispatch
"As an organizer, I want to pick a slot with one tap and have invites sent automatically so that I avoid back‑and‑forth scheduling."
Description

Enable selection of any ranked slot with a single action that creates and sends standardized calendar invites to required/optional attendees, attaches conferencing details, and normalizes local times. Supports tentative holds and automatic release upon finalization to prevent double‑booking. Integrates with TimeTether’s invite service, recurring series management, and audit logs for traceability.

Acceptance Criteria
One‑Tap Selection Sends Standardized Invites
Given a ranked, conflict‑free slot is visible in Instant Clash Check results and the user has organizer permission When the user taps Select on that slot Then TimeTether creates a single calendar event via the Invite Service within 5 seconds And ICS invites are dispatched to all attendees in one operation with HTTP 2xx from the Invite Service And the event uses the standard TimeTether template for title, description, and location And no additional confirmation dialog is required And the system revalidates conflicts at dispatch time; if a conflict is detected, no invites are sent and the user is shown refreshed top alternatives
Correct Handling of Required vs Optional Attendees
Given the selected slot includes required and optional attendees When the invite is created Then required attendees are added with PartStat=NEEDS-ACTION and Role=REQ-PARTICIPANT And optional attendees are added with PartStat=NEEDS-ACTION and Role=OPT-PARTICIPANT And the attendee lists match the source meeting definition exactly And recipients see their proper required/optional designation in Google Calendar and Outlook
Conferencing Details Auto‑Attachment
Given the organizer has a default conferencing provider configured or a TimeTether bridge is available When the invite is dispatched Then a joinable conferencing link and dial‑in (if available) are attached to the event And the link is unique per meeting (not reused across unrelated events) And the conferencing join works for at least one external attendee account in test And the location field includes the conferencing label
Timezone Normalization in Invites
Given attendees span multiple time zones When the invite is sent Then each recipient sees the local start/end time correctly rendered in their calendar client And the ICS contains VTIMEZONE or TZID entries consistent with the meeting time zone And the event description includes a universal time reference (UTC) for audit clarity
Tentative Holds and Automatic Release
Given the user taps Hold instead of Send When the hold is placed Then tentative placeholder events are created on all required attendees’ calendars with a Hold label And competing slots from the same scheduling decision are also held if configured And when the user finalizes a single slot, only that slot is converted to the final event and all other holds are removed within 5 seconds And if no finalization occurs by the hold timeout, all holds are automatically released And no attendee ends up double‑booked due to overlapping holds from the same decision
Recurring Series Creation from Selected Slot
Given the selected slot represents a recurring meeting pattern When the user taps Select Then an event series is created with a correct RRULE matching the pattern And invites are sent for the series with the standardized template And exceptions provided by Instant Clash Check are created as EXDATE or overridden instances And attendees see the full series in Google Calendar and Outlook with correct recurrence and exceptions
Audit Logging and Traceability
Given audit logging is enabled When a user selects a slot, places holds, finalizes, or dispatches invites Then an audit record is written for each action with timestamp, actor, meeting ID, slot ID, and correlation ID And invite service request/response metadata (status code, error code if any) is captured And audit entries are queryable by meeting ID and actor within 1 second of action And failed dispatches include error details sufficient for support triage
Timezone & Work Window Normalization
"As a remote attendee, I want the system to consider my local work hours and DST so that suggested times are reasonable for me."
Description

Model each participant’s home timezone, typical work window, and daylight saving transitions. Convert candidate slots into local hours, excluding times outside defined windows unless explicitly permitted. Provide per‑meeting exceptions and personal preferences (e.g., ‘earliest acceptable start’). Integrates with profile settings and fairness scoring to reduce after‑hours meetings and align with TimeTether’s equitable scheduling goals.

Acceptance Criteria
Correct local-time conversion across DST for all participants
Given candidate UTC slots span dates before, during, and after a DST transition for at least one participant When normalization computes each participant’s local time for each slot Then the local time uses the correct IANA timezone offset for the slot date with 0-minute error And any slot that maps to a nonexistent or duplicated local hour due to DST is excluded by default And the computed UTC offset used per participant per slot is stored and returned via API
Enforce personal work windows and earliest/latest preferences
Given each participant has a profile with home timezone, standard work window (start/end), and personal preferences (earliest acceptable start, latest acceptable end) When a candidate slot is evaluated for inclusion Then the slot is considered valid only if all participants’ local times fall within their personal earliest/latest constraints And slots outside any participant’s window are excluded from the conflict-free set And the reason for exclusion (e.g., before earliest start, after latest end) is recorded per participant per slot
Apply per-meeting exceptions without altering profiles
Given a meeting-level exception extends Participant P’s acceptable window by X minutes for this meeting only When normalization runs for that meeting’s candidate slots Then slots within the exception range are included for P and flagged as exception-applied And P’s profile settings remain unchanged globally And removing the exception immediately re-excludes those slots on re-evaluation
Honor profile updates and precedence over device timezone
Given a participant updates their home timezone or work window in profile settings When normalization next runs Then the new settings are used within 5 minutes of update and any cached timezone/work-window data is invalidated And if device timezone differs from profile timezone, the profile timezone is used for work-window evaluation And all subsequent API responses reflect the updated timezone ID and offsets
Integrate normalized hours into fairness scoring
Given two candidate slots with identical attendees and no calendar conflicts When computing fairness scores Then the slot with fewer total minutes outside participants’ windows has a strictly higher fairness score And any slot where all participants are within-window has zero after-hours penalty component And minutes allowed solely by a per-meeting exception are treated as within-window for that meeting’s fairness score
Expose normalized local times in UI and API
Given conflict-free candidate slots are displayed to a user or returned via API When presenting each slot Then the viewer sees the slot in their local time and can view each participant’s local time And the API includes, per slot per participant: timezone ID, local start/end, window_inclusion flag, and reason if false And all timestamps are ISO 8601 with explicit UTC offsets
External Calendar Connectors & Webhooks
"As an admin, I want reliable, secure connections to our calendars with real‑time updates so that conflict checks stay accurate without manual refreshes."
Description

Offer OAuth-based connectors for Google Calendar and Microsoft 365/Outlook using least‑privilege scopes. Subscribe to push notifications/webhooks for event changes to drive immediate re‑scans; fallback to adaptive polling on failures. Handle token lifecycle (refresh, revocation), consent screens, and per‑domain admin controls. Integrates with TimeTether’s identity and secrets management for secure, reliable data flow.

Acceptance Criteria
Least-Privilege OAuth Connect (Google & Microsoft)
Given a user initiates Google Calendar connect, When the provider consent screen is displayed, Then only scope "https://www.googleapis.com/auth/calendar.readonly" is requested. Given a user initiates Microsoft 365/Outlook connect, When the provider consent screen is displayed, Then only scope "Calendars.Read" is requested. Given consent is granted, When tokens are stored, Then access and refresh tokens are saved in the secrets manager with encryption-at-rest and access limited to the connector service role. Given a calendar connection is established, When events are retrieved, Then only the minimum fields required for conflict detection are persisted (eventId, start, end, timeZone, transparency/busy, recurrence, attendees) and no event description or body is stored. Given a user denies consent, When the connect flow completes, Then no tokens are stored and the UI shows a clear error explaining that access was not granted. Given internal configuration changes, When an attempt is made to request any write scopes, Then the connection attempt is blocked and an admin-visible policy violation is logged.
Realtime Event Change Subscriptions and Re-scan Trigger
Given a connected calendar, When a create/update/delete event occurs, Then the system receives and 2xx-acknowledges the provider webhook within 2 seconds. Given a webhook is received, When it is validated, Then TimeTether triggers an Instant Clash Check re-scan for impacted meetings within 5 seconds of receipt. Given a re-scan is triggered, When ranking is recomputed, Then the top conflict-free options are updated and available via API/UI within 10 seconds of re-scan start. Given duplicate webhook deliveries occur, When messages are processed, Then processing is idempotent and no duplicate re-scans are initiated for the same change. Given an expiring subscription, When the remaining TTL is less than the renewal threshold, Then the subscription is renewed at least 10 minutes before expiration without gaps in coverage. Given a provider challenge/validation request, When received, Then the system responds per provider spec within the required timeframe.
Adaptive Polling Fallback on Webhook Failure
Given webhook delivery failures exceed the threshold or a subscription becomes inactive, When health checks run, Then adaptive polling activates within 1 minute. Given adaptive polling is active, When no recent changes are detected, Then the polling interval increases with exponential backoff and jitter between 15 seconds and 5 minutes while respecting provider rate limits. Given adaptive polling is active, When a calendar change is detected, Then an Instant Clash Check re-scan is triggered and 95th percentile time from provider change to re-scan start is <= 2 minutes. Given webhook health is restored, When verified healthy for the stabilization window, Then polling is disabled and webhook-driven operation resumes within 5 minutes. Given a provider rate limit response is received, When subsequent requests are scheduled, Then the system backs off per Retry-After guidance with jitter and no data is lost. Given a transient network outage occurs, When retries are executed, Then retries follow capped exponential backoff and surface metrics for failures and recoveries.
Token Lifecycle: Refresh, Revocation, Disconnect
Given an access token is within 5 minutes of expiry, When an API call is needed, Then the system refreshes the token using the refresh token and the call succeeds without user interaction. Given a refresh token is revoked at the provider, When the next API call is attempted, Then the connection is marked revoked within 5 minutes, all pulls/subscriptions stop, and the user is notified to reconnect. Given a user selects Disconnect in TimeTether, When the action is confirmed, Then provider subscriptions are deleted/revoked and all tokens are purged from storage within 60 seconds with an audit log entry recorded. Given a secrets encryption key rotation event occurs, When tokens are accessed next, Then tokens are re-encrypted with the new key transparently without service downtime.
Per-Domain Admin Controls and Policy Enforcement
Given an org admin disables the Google connector for domain example.com, When a user from that domain attempts to connect, Then the attempt is blocked with a policy message and no tokens are stored. Given existing connections for a domain are set to Suspended by an admin, When the policy is applied, Then all associated webhooks and polling are halted within 15 minutes and access attempts are denied. Given an admin requires approval for new connections, When a user submits a connect request, Then the request appears in the admin queue and no tokens are exchanged until approval is granted. Given an admin restricts scopes to read-only, When any configuration attempts to add write scopes, Then the system prevents it and logs a policy violation to the admin audit trail.
Secure Secrets Management and Data Isolation
Given tokens are persisted, When stored, Then they are encrypted at rest using KMS-managed keys and access is restricted to the connector service IAM role; plaintext tokens never appear in logs. Given a connector process needs a token, When the token is retrieved, Then it is loaded into memory only, never written to disk, and is zeroed from memory after use. Given a multi-tenant environment, When a request is made with an org identity, Then only that org’s tokens and calendar data are accessible; cross-tenant access attempts are denied and audited. Given the secrets manager is unavailable, When token access is attempted, Then the operation fails closed, no tokens are returned, and automatic retries with backoff are performed without leaking secrets.
Observability, Auditing, and Reliability SLOs
Given a user connects or disconnects a calendar, When the operation completes, Then an immutable audit log event is recorded with timestamp, actor, provider, org, and outcome. Given a webhook receipt leads to a re-scan, When tracing is enabled, Then correlated trace/span IDs link webhook receipt, re-scan job, and ranking update across services. Given sustained webhook delivery failures, When p99 failure rate exceeds 1% for 5 consecutive minutes, Then an alert is sent to on-call and status is reflected on the connector health dashboard. Given normal webhook operations, When measuring monthly, Then >= 99.5% of provider event changes are reflected in Instant Clash Check results within 30 seconds of provider notification; under polling fallback, >= 95% within 2 minutes of provider change. Given duplicate or out-of-order provider messages, When processed, Then deduplication and ordering logic prevent double-processing and ensure eventual consistency.
Performance & Scalability Guarantees
"As a user, I want Instant Clash Check to return top options almost instantly so that I can schedule without waiting."
Description

Deliver median end‑to‑end conflict scan and ranking under 1 second for up to 20 participants, with progressive results and under‑3‑second P95 up to 50 participants. Support large groups (up to 200) via batching, caching, and incremental ranking. Provide system metrics, rate limiting, and graceful degradation. Integrates with TimeTether observability to expose SLIs/SLOs and alerting for latency regressions.

Acceptance Criteria
Median <1s end-to-end scan and ranking for ≤20 participants
Given a conflict scan request with 5–20 participants and connected calendars When Instant Clash Check executes a full conflict scan and ranking for a 4‑week horizon Then the measured end‑to‑end latency (request received to first complete ranked response) has a median < 1000 ms over 1000 requests in a controlled perf environment And at least the first progressive result is delivered within 500 ms for ≥99% of those requests And no request returns an HTTP 5xx under nominal conditions (calendar providers healthy)
P95 ≤3s with progressive results for 21–50 participants
Given a conflict scan request with 21–50 participants, realistic focus blocks and blackout periods When executing 1000 requests at steady load with 10 concurrent clients Then the p95 end‑to‑end latency is ≤ 3000 ms And the first progressive chunk is delivered within 800 ms for ≥95% of requests And no request exceeds 10 seconds; those that would exceed return partial results and a degraded flag
Large‑group support up to 200 participants via batching, caching, and incremental ranking
Given a conflict scan request with 51–200 participants When the scan runs under 10 concurrent requests with warmed caches for repeated participants Then the first batch of ≥10 ranked options is returned within 5 seconds And subsequent batches of ≥10 options are streamed at least every 3 seconds until 100 options or horizon exhaustion And cache hit ratio for repeated scans with unchanged inputs within 15 minutes is ≥60% And external calendar API calls do not exceed provider rate limits; exponential backoff and queuing are applied without request failure
Rate limiting and back‑pressure enforcement
Given a client that sends scans above a quota of 10 requests/minute with a burst capacity of 20 When additional requests exceed the quota Then the service responds with HTTP 429 and a Retry‑After header And in‑quota requests maintain SLOs (median <1s for ≤20 participants, p95 <3s for ≤50 participants) under 50 concurrent users And metrics include rate_limit_exceeded_count and per‑client rejected count
Graceful degradation on upstream latency or errors
Given upstream calendar providers throttle or time out, or internal queues near saturation When a scan is requested for any group size up to 200 Then the service returns partial, clearly labeled results within the applicable SLO window whenever possible And the response includes degraded=true, a machine‑readable cause code, and a list of missing/partial data sources And the system automatically narrows time horizon or sampling to maintain ≤3s p95 for ≤50 participants And a structured event is emitted for observability with correlation IDs
Observability SLIs/SLOs and alerting for latency regressions
Given production traffic at ≥1 request/second over a 30‑minute window When observability is enabled Then latency SLIs are emitted at 1‑minute resolution segmented by participant buckets (≤20, 21–50, 51–200) And SLOs are defined: median <1s for ≤20 and p95 <3s for 21–50 with ≥99% compliance per rolling 24h And alerts fire within 5 minutes when any bucket violates its SLO for 2 consecutive windows, paging on‑call And dashboards display latency histograms, error rates, queue depth, cache hit ratio, and rate‑limit events

QuickSend Invites

Auto‑generates clean invites with agenda snippets, Meet/Zoom links, and one‑tap RSVP via Slack/Teams. Dispatch in a click and watch acceptances roll in—your recurring series is live in under a minute.

Requirements

One-Click Invite Dispatch
"As a remote product lead, I want to send complete invites with one click so that I can launch a recurring series without manual assembly or context-switching."
Description

Enable a single action that assembles attendees from the selected TimeTether meeting, generates a standardized calendar event with title, time (timezone-normalized), agenda snippet, and conferencing link, and dispatches invites simultaneously via email and integrated chat (Slack/Teams). Leverage the existing scheduling output to ensure the chosen slot respects work windows and fairness rotation. Include idempotent send, retry with exponential backoff, rate limiting, and audit logs. Support multi-tenant branding settings and role-based permissions to restrict who can send on behalf of a team or meeting owner. Ensure sub-60s end-to-end performance from click to invites sent.

Acceptance Criteria
One-Click Invite Dispatch Creates and Sends Standardized Event
Given a finalized TimeTether meeting instance with a selected slot and attendee list and active email and Slack/Teams integrations When an authorized user clicks "Send Invites" once Then the system creates a single calendar event with a standardized title, the selected start/end time, an agenda snippet, and a valid conferencing link per tenant default (Meet/Zoom) And invites are dispatched via both email and Slack/Teams for all attendees And email and chat dispatches begin within 5 seconds of each other and complete end-to-end within 60 seconds of the click for up to 200 recipients And each invite includes one-tap RSVP (Accept/Decline/Maybe) that updates the attendee’s response status in TimeTether And the sender sees a confirmation with per-channel sent counts
Timezone Normalization and Fairness Compliance
Given the meeting slot and fairness rotation produced by TimeTether scheduling When generating the calendar event and invites Then the event time in UTC must match the scheduling output exactly and cannot be altered at send time And each recipient’s invite renders the correct local time based on the recipient’s timezone profile And the system blocks sending if the selected slot violates attendee work window or fairness rotation constraints, showing an error to the sender And the audit log records the UTC slot, each recipient timezone used, and a fairness-rotation identifier
Idempotent Send Prevents Duplicate Invites
Given a meeting instance and deduplication key composed of meetingId + slotId + attendeeHash + brandingVersion + channelSet When "Send Invites" is triggered multiple times within a 10-minute idempotency window with identical inputs Then only one calendar event is created and only one set of email and chat messages is sent And subsequent identical requests return a success result indicating "already sent" with no additional messages or events created And downstream provider messageIds remain unchanged across replays and are linked in the audit log
Retry with Exponential Backoff and Partial Failure Reporting
Given transient provider errors (HTTP 429/5xx or timeouts) from email or chat APIs during dispatch When sending invites Then the system retries up to 3 times per failing recipient/channel with exponential backoff (approximately 1s, 2s, 4s with jitter) And permanent errors (4xx excluding 429) are not retried and are marked as failed And successful deliveries are not duplicated during retries And upon completion the sender sees a summary with per-channel success/failure counts and a "Retry failed" option for remaining failures And the audit log captures each attempt with outcome and latency
Provider Rate Limiting Observance Without User Impact
Given provider-specific rate limits configured per tenant for email and Slack/Teams When dispatching to up to 200 recipients Then outbound requests are paced to remain under provider limits and avoid 429 responses under normal conditions And if throttling occurs, the dispatcher queues requests while ensuring the batch completes within 60 seconds when within the 200-recipient threshold And if limits prevent full completion within 60 seconds, the UI presents remaining pending counts with ETA while background sends complete, and the audit log reflects pending->completed transitions
Audit Logging and Traceability
Given any invite dispatch attempt When the process completes (success, partial, or failure) Then an immutable audit record is written including: timestamp, actor userId, tenantId, meetingId, slotId, attendeeCount, channels, correlationId, idempotencyKey, provider messageIds per channel, per-channel outcomes, and total latency ms And audit entries are queryable by meetingId and correlationId within 5 seconds of completion And audit export API supports tenant-scoped access with pagination and returns all fields above
Role-Based Permissions and Multi-Tenant Branding
Given tenant branding and role-based permissions configured When a user attempts to send invites Then only users with Organizer or Delegate roles possessing "send_invites" permission for the meeting’s team can dispatch; others see a disabled control and receive an "insufficient permissions" error on API call And if sending on behalf of a meeting owner, the From identity (email/chat) uses the owner’s identity only when the user has "send_on_behalf" permission; otherwise the user’s own identity is used And invites and chat messages consistently apply the tenant’s branding (logo, colors, footer, link domain), and the branding version applied is recorded in the audit log And cross-tenant isolation prevents sending to or on behalf of another tenant
Smart Agenda Snippet Builder
"As an engineering manager, I want a pre-filled, concise agenda in my invite so that attendees know the objectives without me writing it each time."
Description

Auto-generate concise agenda snippets from TimeTether meeting context (title, purpose, participants, past notes) using templated sections and optional AI summarization. Enforce brevity (e.g., 2–4 bullets, 280–500 chars), sanitize formatting for calendar and chat surfaces, and support per-team templates with tokens (e.g., {objective}, {owner}, {docs_link}). Provide inline edit before send, persistence of last-used template per meeting type, and localization. Ensure no sensitive data leakage by redacting marked fields and respecting tenant data policies.

Acceptance Criteria
Template Selection, Tokens, and Persistence
Given the user belongs to a team with at least one agenda template containing tokens (e.g., {objective}, {owner}, {docs_link}) And the user has previously used the "Sprint Planning" meeting type with template "Alpha-Plan v2" When the user opens QuickSend for a new "Sprint Planning" meeting and clicks Generate Snippet Then "Alpha-Plan v2" is pre-selected as the template And all tokens present in the template are resolved from the meeting context (title, purpose, participants, past notes metadata) And any token without a value results in its line being omitted without leaving braces or empty bullets And no unresolved token text like {token_name} appears in the output.
AI Summarization Toggle and Context Usage
Given AI summarization is enabled And past meeting notes exist for this series When the user generates the snippet Then the snippet includes a concise summary derived from past notes in the intended template section And if AI summarization is disabled, no content from past notes is used beyond explicit non-AI fields And toggling the AI switch does not change the selected template or persisted last-used template for the meeting type.
Brevity and Bullet Count Enforcement
Given the template and context could produce a long agenda When the snippet is generated Then the output contains 2–4 bullets And the total character count is between 280 and 500 characters inclusive And truncation preserves complete words and valid URLs And no empty or duplicate bullets are present.
Formatting Sanitization for Calendar and Chat Surfaces
Given the generated snippet may include bullets, links, and inline markup When the snippet is inserted into a Google Calendar invite body Then it renders as plain text with standard hyphen bullets and clickable links, with no visible HTML tags or unsupported markup When the snippet is posted via Slack/Teams Then bullets render correctly for the platform and links display as platform-native links or plain URLs, with no unsupported markup visible.
Inline Edit Before Send
Given a generated snippet is displayed in the QuickSend compose area When the user edits the text inline and clicks Send Then the edited text is sent identically in the calendar invite and Slack/Teams message And the edit applies only to this send and does not modify the underlying template And future generations for this meeting type continue to use the selected template unless the user changes it.
Localization of Snippet Content
Given the tenant default locale is fr-FR and the user's preferred language is French When the snippet is generated Then template static text, token labels, and date/time formats appear in French using fr-FR conventions And if no localized template variant exists, the system falls back to the default template while localizing token labels and date/time And right-to-left languages render bullets and punctuation correctly when applicable.
Sensitive Data Redaction and Tenant Policy Compliance
Given certain context fields are marked Sensitive or do_not_share by tenant policy and past notes contain lines tagged [CONFIDENTIAL] When the snippet is generated Then all sensitive tokens and tagged content are omitted or replaced with [REDACTED] And no sensitive phrases or values from marked fields appear in the final snippet And AI summarization excludes redacted content from its inputs and outputs.
Auto-Generated Video Conferencing Links
"As a distributed team lead, I want invites to include a ready-to-join video link so that no one has to hunt for or create a meeting room."
Description

Integrate with Google Meet and Zoom to create unique conferencing links for each occurrence at send time, aligned with team defaults and compliance policies (passcodes, waiting rooms, domain-restricted join). Handle OAuth connection, token refresh, and graceful fallback to the secondary provider if the primary fails. Embed join link, dial-in (if available), and meeting ID in both calendar invites and chat messages. Support policy controls such as provider allow/deny lists and region constraints.

Acceptance Criteria
Per-Occurrence Unique Link Generation at Send Time
Given a recurring series with N scheduled occurrences and provider defaults configured, When the user clicks QuickSend to dispatch invites, Then the system must create N distinct meetings (unique meeting IDs/URLs) — one per occurrence — at send time, not before. Given a recurring series, When invites are dispatched, Then each occurrence’s join URL must be unique across the series and not reused across different series. Given an occurrence is updated (time/agenda) without selecting Regenerate Link, When the invite is re-sent, Then the original meeting link and meeting ID for that occurrence remain unchanged. Given an occurrence is updated and the user selects Regenerate Link, When the invite is re-sent, Then a new meeting is created and the old link is invalidated or removed from the invite if the provider supports it. Given network and provider are healthy, When QuickSend executes, Then each meeting link must be created and embedded within 3 seconds per occurrence (P95), with no more than one provider API call per occurrence for creation.
OAuth Connection and Token Refresh for Google Meet and Zoom
Given a connected provider account with an access token that is expired, When QuickSend attempts to create a meeting, Then the system must refresh the token using a valid refresh token and proceed without user intervention. Given a provider returns 401/invalid_token and a refresh token is present, When a refresh attempt is made, Then the system must obtain a new access token on the first retry and use it to successfully create the meeting. Given a provider returns 401/invalid_token and no refresh token is present or refresh fails, When QuickSend cannot obtain a new token, Then the user is prompted to re-authenticate and the send is blocked only for the affected provider. Given tokens are refreshed, When the process completes, Then tokens are stored securely and no secret material appears in logs or chat/calendar payloads. Given token refresh is successful, When subsequent meeting creations occur within the same session, Then no additional user prompts for auth are shown.
Automatic Fallback from Primary to Secondary Provider
Given the team default provider is Provider A and secondary is Provider B, When meeting creation on Provider A returns a non-retryable failure (e.g., policy violation, 4xx other than 409/429) or exceeds timeout of 5 seconds after 2 retries, Then the system must automatically attempt creation on Provider B and proceed if successful. Given a fallback occurs, When invites and chat messages are sent, Then they must reference Provider B’s join details and clearly indicate the selected provider in the metadata sent to the calendar and chat platform (not visible to attendees). Given Provider A ultimately succeeds after fallback has already succeeded on Provider B, When race conditions could create duplicates, Then the system must ensure only one meeting link is embedded (Provider B) and any duplicate creation on Provider A is cleaned up or canceled. Given both providers fail or are disallowed by policy, When QuickSend is invoked, Then the send is blocked and the user receives an actionable error stating the reason codes for both providers.
Embedding Join Details in Calendar Invites and Chat Messages
Given a meeting is successfully created, When the calendar invite is generated, Then the join URL must be placed in Location and Description fields, and include meeting ID and passcode (if applicable) in the Description. Given provider supplies dial-in numbers, When the invite is generated, Then at least one dial-in number and any required meeting ID/passcode must be included in the Description; if dial-in is unavailable, Then a clear note states that dial-in is not available. Given a Slack or Teams message is sent for the occurrence, When the message is delivered, Then it must contain: join URL, meeting ID, passcode (if applicable), and a one-tap RSVP action reflecting Accept/Maybe/Decline. Given localization settings, When messages and calendar invites are created, Then date/time and phone number formats follow the recipient’s locale. Given the meeting is updated or link regenerated, When updates are dispatched, Then both calendar and chat surfaces are updated within 10 seconds and no stale links remain in the latest message or event.
Applying Provider-Specific Compliance Policies at Creation
Given organization policies require passcodes and waiting rooms, When creating Zoom meetings, Then passcode=enabled and waiting_room=enabled must be set according to policy/role defaults, and validated via Zoom settings in the creation response. Given organization policies require domain-restricted join, When creating Google Meet meetings, Then the meeting must be restricted to the organization’s domain according to Google Workspace controls, or the send must be blocked with an actionable error if restriction cannot be applied. Given team-level defaults (e.g., preferred provider, default waiting room/passcode), When meetings are created, Then provider-specific settings must match the team defaults unless overridden by stricter org policy, in which case org policy prevails. Given conflicting policies (e.g., waiting room disabled by team default but required by org), When a meeting is created, Then the stricter policy is applied and the applied policy set is recorded in the meeting metadata. Given a policy cannot be satisfied for the selected provider, When QuickSend runs, Then the system must either apply an equivalent control on the provider or fail fast and attempt fallback to the secondary provider before blocking the send.
Policy Controls: Provider Allow/Deny Lists and Region Constraints
Given a policy deny-lists Zoom, When QuickSend attempts meeting creation, Then Zoom must not be called and the system selects the next allowed provider automatically; if none allowed, the send is blocked with an error citing policy ID and reason. Given a policy allow-lists only Google Meet, When QuickSend runs, Then only Google Meet is used for meeting creation regardless of team defaults. Given region constraints (e.g., EU-only), When creating meetings and selecting dial-in numbers, Then the provider endpoint/region and published dial-in numbers must comply with the allowed regions, or the send must be blocked with an explicit region-violation error. Given policy updates take effect, When a send is initiated after a policy change, Then the new policy must be enforced within 1 minute with no stale cache decisions. Given a policy decision is made, When the meeting is created or blocked, Then the decision outcome (provider chosen, region applied, or violation) is recorded for audit with a stable correlation ID for the series and occurrence.
Idempotent Sends, Retries, and Duplicate Prevention
Given QuickSend is invoked with a specific series occurrence, When transient errors occur (e.g., 429, 5xx), Then the system retries with exponential backoff up to 3 attempts and logs the attempt count. Given QuickSend is retried by the user or by the system, When an idempotency key for the series occurrence is provided, Then at most one provider meeting is created and the same join link is reused across retries. Given a duplicate provider meeting is detected (same idempotency key or same external reference), When reconciling state, Then only one link is embedded in the invite/chat, and extra meetings are automatically canceled where supported. Given the user cancels the send before completion, When reconciliation runs, Then no meetings remain created for the canceled occurrences. Given all retries fail, When the process completes, Then the user receives a single consolidated failure with the final error and guidance to re-authenticate, change provider, or adjust policy settings.
One-Tap RSVP via Slack/Teams
"As a remote engineer, I want to RSVP to a meeting from Slack in one tap so that I can confirm attendance without leaving my current workflow."
Description

Deliver interactive Slack/Teams messages with Accept, Decline, and Tentative actions that update attendance in real time. Use secure, signed callbacks and user-to-calendar identity mapping to write responses back to the calendar event. Provide a magic-link flow for external guests without workspace accounts. Post confirmation and allow optional reason on decline. Ensure accessibility, rate limiting, and graceful degradation to email RSVP if chat delivery fails.

Acceptance Criteria
Slack One‑Tap RSVP Real‑Time Update
Given an invited attendee with a mapped calendar identity receives a Slack interactive message containing Accept, Tentative, and Decline actions for a meeting When the attendee clicks Accept Then the attendee's calendar status is set to Accepted within 5 seconds and the organizer's attendee list reflects the change And a confirmation reply is posted in the same Slack thread within 3 seconds showing the new status And the interactive buttons update to reflect the selection and become disabled for that user When the attendee clicks Tentative Then the calendar status is set to Tentative within 5 seconds and a confirmation reply is posted within 3 seconds When the attendee clicks Decline Then a decline-reason prompt is displayed within 2 seconds and no calendar update occurs until the user submits or skips the reason
Teams One‑Tap RSVP Real‑Time Update
Given an invited attendee with a mapped calendar identity receives a Microsoft Teams adaptive card with Accept, Tentative, and Decline actions for a meeting When the attendee selects any action Then the attendee's calendar status reflects the chosen action within 5 seconds and a confirmation card update is shown within 3 seconds And the action buttons become disabled or reflect the chosen state to prevent duplicate submissions
Secure Signed Callbacks and Idempotency
Given the RSVP callback includes platform signature and timestamp headers When the signature validates against the current or immediately previous signing secret and the timestamp is within 5 minutes Then the callback is processed When the signature is missing, invalid, or the timestamp is outside the 5‑minute window Then the request is rejected with HTTP 401 and no calendar updates occur Given duplicate interactions with the same interaction ID are received within 10 minutes When processed Then only the first interaction updates the calendar and subsequent duplicates return HTTP 200 with no state change
User‑to‑Calendar Identity Mapping and Linking
Given a Slack/Teams user clicks an RSVP action and no verified mapping to a calendar identity exists When a prior mapping is found Then the RSVP proceeds using that calendar identity When no mapping is found Then the user receives a DM to link their calendar via OAuth and the RSVP action is queued for up to 10 minutes And upon successful link within 10 minutes, the queued RSVP is applied and a confirmation is posted in the original thread And if linking fails or times out, an email RSVP is sent within 2 minutes and the user is notified in‑thread
External Guest Magic‑Link RSVP
Given an invitee without access to the organizer’s Slack/Teams workspace is added to the event When the invite is sent Then the invitee receives an email containing a single‑use, signed magic link that expires in 7 days or upon first use When the invitee opens the link Then a web page displays Accept, Tentative, and Decline options and, on Decline, an optional reason field When the invitee submits a response Then the calendar attendee status updates within 5 seconds and a confirmation email is sent within 2 minutes When the link is expired, already used, or tampered Then the page shows an invalid/expired link message (HTTP 410) and offers a request‑new‑link option
Decline Reason Capture and Organizer Visibility
Given an attendee chooses Decline in Slack, Teams, or the magic‑link web page When prompted for an optional reason up to 500 characters Then the reason is saved with the RSVP if provided and any HTML/script content is sanitized And the organizer can view the decline reason in TimeTether and in the calendar event notes within 10 seconds And other attendees cannot see the reason by default And if no reason is provided, the RSVP still completes successfully
Accessibility, Rate Limiting, and Graceful Degradation
Given interactive messages are rendered in Slack/Teams Then all actionable elements include accessible names and roles, pass automated accessibility checks, and meet 4.5:1 color contrast And keyboard‑only navigation can activate all actions and modals without traps Given outbound message delivery returns HTTP 429 or a network error When retrying Then the system respects platform Retry‑After headers and retries up to 3 times with exponential backoff And if delivery ultimately fails, an email RSVP is sent within 2 minutes and the failure is logged for monitoring Given a single user triggers multiple RSVP interactions When more than 5 interactions occur within 60 seconds Then additional interactions are rejected with HTTP 429 and a user‑facing notice is posted without performing calendar writes
Real-Time Acceptance Tracking & Sync
"As a program lead, I want to see who has accepted and be alerted when quorum is met so that I can proceed confidently with the meeting plan."
Description

Track and display invite delivery status and attendee responses across email and chat, updating the source calendar event and TimeTether’s meeting record in real time. Surface a lightweight dashboard and notifications when quorum is reached or critical stakeholders decline. Handle per-occurrence responses for recurring series, ICS updates, cancellations, and re-sends on time or content changes. Provide webhooks and analytics events for downstream reporting.

Acceptance Criteria
Real-Time Cross-Channel Delivery and RSVP Tracking
Given an invite is dispatched via email and Slack/Teams, when provider delivery receipts, bounces, or RSVP actions are received, then TimeTether updates per-channel delivery and attendee response status within 10 seconds on the meeting dashboard. Given duplicate provider events are received, when processing them, then state updates are idempotent and no duplicate timeline entries are created. Given a temporary delivery failure occurs, when retry policy runs, then the system retries up to 3 times over 15 minutes and flags "Undeliverable" after final failure.
Calendar Event and TimeTether Record Synchronization
Given an attendee changes RSVP via one-tap in email or Slack/Teams, when the change is processed, then the linked Google/Outlook calendar attendee status reflects the same value within 10 seconds. Given an attendee changes RSVP in the source calendar UI, when the calendar webhook is received, then TimeTether updates the meeting record within 10 seconds and shows the change in the dashboard timeline. Given network or provider errors occur, when synchronization resumes, then the system reconciles the latest authoritative status using most-recent-timestamp precedence and records the resolution outcome.
Quorum Reached and Critical Decline Notifications
Given a meeting has configured quorum rules and critical stakeholders, when acceptances meet or exceed the quorum threshold for a specific occurrence, then an organizer notification is sent via Slack/Teams and email within 10 seconds and a "Quorum reached" badge appears on the dashboard. Given any designated critical stakeholder declines, when their decline is recorded, then an organizer alert is sent within 10 seconds naming the stakeholder and the affected occurrence. Given multiple rapid status changes, when a threshold is crossed repeatedly, then only one notification per occurrence per threshold crossing is emitted (idempotent).
Per-Occurrence Responses in Recurring Series
Given a recurring series is scheduled, when an attendee responds to a single occurrence, then the response is stored at the occurrence level without altering other instances. Given an attendee selects "this and following," when applied starting at occurrence N, then the response applies to all future occurrences and leaves past occurrences unchanged. Given occurrence-level exceptions exist, when syncing/exporting to calendar/ICS, then exception instances are created with correct UID/RECURRENCE-ID and attendee status values.
ICS Update Propagation and Sequence Handling
Given the organizer updates time or content for an occurrence, when the change is saved, then an ICS REQUEST with the same UID and incremented SEQUENCE is sent to all attendees within 10 seconds. Given only non-time content changes (agenda/links/description) are made, when updates are sent, then prior attendee acceptance is preserved in TimeTether and the source calendar. Given the start/end time changes, when updates are sent, then previously accepted attendees are flagged "Reconfirm requested" and receive a one-tap update link. Given a cancellation is performed, when processed, then a METHOD:CANCEL is sent with matching UID/RECURRENCE-ID and the event is marked Cancelled in both TimeTether and the source calendar.
Cancellations and Re-Sends on Time/Content Changes
Given the organizer cancels an occurrence or entire series, when the action is confirmed, then cancellation messages are dispatched across all channels within 10 seconds, delivery statuses are tracked, and the dashboard reflects "Cancelled" for the scope selected. Given content or time changes trigger a re-send, when the re-send is initiated, then updated invites are dispatched with refreshed ICS and message snippets, preserving RSVP tokens and invalidating superseded links. Given a re-send occurs within 5 minutes of a prior send, when recipients have already responded, then their response is preserved unless the time changed and reconfirmation is requested.
Webhooks and Analytics Event Emission
Given any of the following events occur—delivery status change, RSVP change, quorum reached, critical decline, update sent, cancellation sent—when the event is recorded, then a webhook is POSTed within 10 seconds with HMAC-SHA256 signature, idempotency key, and ISO 8601 timestamps. Given the webhook endpoint returns non-2xx, when retrying, then the system retries for up to 24 hours with exponential backoff and jitter, preserving event order per meeting. Given analytics are enabled, when events occur, then analytics events include meeting_id, occurrence_id, attendee_id, channel, old_status, new_status, and latency_ms fields and are available for export within 5 minutes.
Instant Recurring Series Bootstrapping
"As a product lead, I want the entire recurring series live immediately so that my team gets all invites at once with minimal coordination overhead."
Description

Upon initial send, create the full recurring series within TimeTether using fairness rotation and participants’ work windows, pre-generating invites for the next N occurrences. Ensure each occurrence has its own conferencing link, agenda placeholder, and correct timezone rendering per attendee. Allow per-occurrence edits and automatic propagation of template changes. Guarantee that creating and dispatching the initial series completes in under one minute under typical load.

Acceptance Criteria
One-Click Series Creation Under 60 Seconds
Given a user selects required attendees with valid work windows and clicks QuickSend on the first invite When TimeTether generates the recurring series Then the system creates and dispatches the initial occurrence and pre-generates invites for the next N occurrences where N equals the workspace default (e.g., 8) unless overridden by the user And the end-to-end operation completes within 60 seconds under typical load, measured from click to confirmation And a success message displays the series ID And an audit log entry records start/end timestamps, N, attendee count, and duration
Work Window Enforcement Across Timezones
Given each required attendee has a configured local timezone and work window When the series is generated Then every occurrence's start and end time for each required attendee falls within their work window And if no time exists satisfying all required attendees, generation fails with a descriptive error before any invites are sent And no after-hours occurrence is created for required attendees
Fairness Rotation for Edge-Overlap Windows
Given two or more required attendees whose work windows overlap only at the edges but allow scheduling within windows And N >= 4 occurrences are requested When the series is generated Then the local start times are rotated such that for any two attendees, the count of edge-slot assignments (first or last 30 minutes of their window) differs by no more than 1 across the N occurrences And no attendee receives edge-slot assignments on more than two consecutive occurrences And the rotation pattern is persisted and visible in the series preview
Unique Conferencing Links Per Occurrence
Given a conferencing provider (Meet or Zoom) is selected When the recurring series is generated Then each occurrence has a unique meeting URL and meeting ID And the URL is embedded in that occurrence's calendar invite and Slack/Teams card And re-generating the series does not reuse previously allocated meeting IDs And deleting an occurrence revokes only its link without affecting other occurrences
Agenda Placeholder and Template Propagation with Local Overrides
Given the series has an agenda template with placeholders and some occurrences may have local edits When the user updates the series-level agenda template text Then all future occurrences without local edits update within 10 seconds And occurrences with local edits retain their content and display a local override indicator And changing the series-level conferencing provider updates links only for future, non-overridden occurrences When the user edits an individual occurrence's start time or agenda Then only that occurrence is updated and affected attendees receive an updated invite; other occurrences remain unchanged
Accurate Timezone Rendering and DST Handling
Given attendees are in multiple IANA timezones with upcoming DST transitions When invites are delivered and opened in Google Calendar, Outlook, and Apple Calendar Then each attendee sees correct local start/end times with the proper timezone identifier And occurrences spanning a DST boundary maintain the intended local time while shifting correctly in UTC And .ics attachments validate without timezone warnings or errors
One-Tap RSVP via Slack/Teams with Per-Occurrence Tracking
Given Slack or Teams is connected for the workspace When a recipient taps RSVP Yes/No/Maybe on an occurrence card Then the response is recorded for that specific occurrence only and reflected in TimeTether and the calendar attendee list within 5 seconds And the responder receives a confirmation message And any series-level RSVP actions do not overwrite existing occurrence-level responses

Active Series Meter

A live counter that tracks how many recurring series are billable right now, with clear status badges (Active, Paused, Dormant-Pending). Finance and admins see real-time exposure at a glance, preventing surprises at month-end and enabling quick housekeeping before charges accrue.

Requirements

Real-time Billable Series Counter
"As a finance manager, I want a live count of billable recurring series so that I can monitor exposure and prevent unexpected charges at month-end."
Description

A streaming counter service that computes, in near real time, how many recurring series are billable “right now.” It ingests schedule and billing rule events, applies eligibility logic (Active = scheduled and not paused/cancelled; Paused = explicitly paused or past-due hold; Dormant-Pending = inactive for the configured dormancy period but scheduled to resume within the policy window), and outputs a single count and per-status breakdown. The service updates the meter within 5 seconds of relevant changes, de-duplicates concurrent updates, and remains accurate across large organizations. Integrates with the scheduling datastore, billing rules engine, and the admin/finance dashboard component. Provides graceful degradation with last-known value and a “stale” indicator if upstream systems are unreachable.

Acceptance Criteria
Dashboard updates within 5 seconds after schedule change
Given a finance user views the Active Series Meter on the admin/finance dashboard When a recurring series is switched from Paused to Active in the scheduling datastore and the change event is emitted Then the total and per-status counts reflect the new classification within 5 seconds without requiring a page refresh And the Active badge count increases by 1 and the Paused badge count decreases by 1 for that series
Paused and past-due hold classification reflected
Given a series is explicitly paused or placed on past-due hold by the billing rules engine When the corresponding event is ingested by the counter service Then the series is excluded from Active and included under Paused within 5 seconds And the total equals Active + Paused + Dormant-Pending
Dormant-Pending classification per policy window
Given dormancy period D and policy window W are configured for the organization And a series has been inactive for at least D and is scheduled to resume within W When events are processed Then the series is classified under Dormant-Pending and not under Active or Paused And if no resumption is scheduled within W, the series is not classified as Dormant-Pending
De-duplication of concurrent duplicate events
Given multiple identical schedule/billing events for the same series_id and version arrive concurrently or out of order When the counter service processes these events Then the meter updates exactly once for that logical change with no transient double-counting And the final counts remain correct regardless of event arrival order
Graceful degradation with last-known value and stale indicator
Given an upstream outage or timeout affects the scheduling datastore or billing rules engine When the dashboard loads or refreshes during the outage Then the meter displays the last-known total and breakdown and shows a visible Stale indicator And upon recovery, the Stale indicator clears and the values refresh to current within 5 seconds
Scalability and accuracy under high load
Given an organization with at least 10,000 recurring series and a sustained rate of 500 relevant events per second for 60 seconds When the system processes the load Then 99th percentile update latency from event ingestion to dashboard update is 5 seconds or less And the meter’s counts match a ground-truth recomputation with 0 misclassified series
Role-based visibility for finance and admins
Given role-based access control is enforced When an Admin or Finance user opens the dashboard Then the Active Series Meter is visible with total and per-status counts And when a non-Admin, non-Finance user opens the dashboard, the Active Series Meter is not visible
Status Badges & Breakdown UI
"As an admin, I want clear status badges and counts for each series state so that I can quickly identify where to focus housekeeping."
Description

A dashboard widget that renders the Active Series Meter with accessible, color-safe status badges (Active, Paused, Dormant-Pending) and a numeric breakdown. Includes hover tooltips with status definitions and most-recent update timestamp, and links to filtered lists of series by status. The widget adapts responsively to varying screen sizes, supports dark mode, and localizes number formatting. The component subscribes to the counter service via WebSocket/SSE and falls back to periodic polling when real-time channels are unavailable.

Acceptance Criteria
Real-Time Updates with Polling Fallback
Given the widget is loaded and a real-time channel (WebSocket or SSE) is available When the server pushes a counter update Then the visible counts and badge statuses update within 2 seconds without page refresh Given the widget is loaded and real-time channels are unavailable When the initial connection attempt fails Then the widget begins polling the counter service every 30 seconds and reflects server changes within 30 seconds of occurrence Given the widget is polling due to unavailable real-time channels When a real-time channel becomes available Then the widget switches to real-time updates within 10 seconds and stops polling Given network connectivity is lost during a session When connectivity is restored Then the widget resumes receiving updates without requiring a page reload
Accessible, Color-Safe Status Badges
Given the widget renders the status badges When statuses are displayed Then badges for Active, Paused, and Dormant-Pending are present with explicit text labels (not color-only) Then each badge’s text and background combination meets WCAG 2.1 AA contrast ratio of at least 4.5:1 in both light and dark modes Then each badge is programmatically labeled for assistive technologies (e.g., aria-label "Active series: <count>") Then each badge is keyboard focusable and operable as a link, and a visible focus indicator with at least 3:1 contrast is shown
Numeric Breakdown and Filtered Navigation
Given counts are available from the counter service When the widget renders Then the total series count and per-status counts are displayed When a status badge is activated via click, Enter, or Space Then navigation occurs to the series list view filtered by that status, and the list’s item count matches the badge count at the moment of activation Given a status count is zero When its badge is activated Then the user is navigated to the empty filtered list (zero results) rather than blocking the link Given the user navigates back from the filtered list When the dashboard is shown again Then the widget state is preserved and shows the latest counts
Tooltips with Definitions and Timestamps
Given a user hovers over or keyboard-focuses a status badge When 150 ms have elapsed without pointer movement Then a tooltip appears showing the status definition and the most recent update timestamp Then the tooltip is accessible (associated via aria-describedby), can be dismissed with Escape, and hides on pointer leave or focus out Then the timestamp reflects the last received update and refreshes immediately after any incoming update Then the tooltip does not obstruct primary click/focus interactions and remains within the viewport on all supported screen sizes
Responsive Layout Across Viewports and Browsers
Given the viewport width is at least 1200 px When the widget renders Then badges and counts are laid out in a single horizontal row without horizontal scrolling Given the viewport width is between 768 px and 1199 px When the widget renders Then the layout adapts to a compact multi-row presentation with touch targets of at least 44x44 px Given the viewport width is less than 768 px When the widget renders Then badges stack vertically, long text truncates with ellipses, and no horizontal scrolling occurs Then across the latest two versions of Chrome, Firefox, Safari, and Edge, the layout renders without clipped text, overlap, or overflow
Dark Mode Theming and Contrast
Given the application theme is set to dark mode When the widget renders Then text, backgrounds, badges, and tooltips use dark theme tokens and maintain at least 4.5:1 contrast When the theme is toggled between light and dark Then the widget updates its colors within 300 ms without page reload or noticeable flash of incorrect theme Then no hard-coded light backgrounds remain visible on badges or tooltips in dark mode
Locale-Aware Number and Time Formatting
Given a user locale (e.g., en-US, fr-FR, ar-EG) is configured When the widget renders numeric counts Then numbers are formatted according to the locale’s numbering conventions (grouping, decimal separator, digits) When the tooltip displays the most recent update timestamp Then the timestamp is formatted using the user’s locale and timezone and reflects 12/24-hour preferences When the user changes their locale preference Then counts and timestamps re-render in the new locale within 1 second without page reload
Billing Timezone Normalization
"As a finance analyst, I want the meter aligned to our billing timezone and cutoffs so that the counts match our invoices and reports."
Description

Logic to evaluate “right now” and billing boundaries using the organization’s billing timezone and workweek configuration. Normalizes timestamps for series activity across participant timezones, handles daylight saving transitions, and aligns meter calculations with invoice cutoffs and proration rules. Stores and respects per-organization settings with sensible defaults and validation.

Acceptance Criteria
Right-Now Computation Uses Organization Billing Timezone
Given an organization’s billing timezone is set to America/New_York And the current UTC instant corresponds to 20:55 in America/New_York And a recurring series is scheduled 20:50–21:20 in America/New_York When the Active Series Meter computes “right now” Then the series is classified Active for the entire 20:50–21:20 window based on the billing timezone And the meter count includes this series regardless of participant local times And the meter’s timestamp indicator reflects the billing timezone
Invoice Cutoff Boundaries and Proration Aligned to Billing Timezone
Given the monthly invoice cutoff is 00:00 at the start of the month in the organization’s billing timezone When local time in the billing timezone rolls from 23:59:59 to 00:00 Then no series is counted in two periods; the meter’s billable period changes exactly at 00:00 billing timezone And proration for mid-cycle activations/deactivations is computed using elapsed minutes bounded by start/end in the billing timezone And the exposure count reported by the meter for the closing period matches the invoice line items for that period
Daylight Saving Time Transitions Do Not Skew Billing
Given the organization’s billing timezone observes daylight saving time When a spring-forward transition skips 02:00–02:59 Then a series scheduled across the missing wall-clock hour remains billable for its scheduled duration without a negative gap And proration uses actual elapsed minutes derived from the wall-clock schedule in the billing timezone When a fall-back transition repeats 01:00–01:59 Then a series spanning the repeated hour is not double-counted; billable minutes in the repeated hour are counted once And period boundaries occur at the correct local times in the billing timezone
Cross-Timezone Series Activity Normalization
Given a recurring series with participants in multiple timezones When determining whether the series is billable “right now” Then the decision uses the series occurrence time normalized to the organization’s billing timezone And participant local off-hours do not affect billable status And series with status Paused or Dormant-Pending are excluded from the Active count
Workweek Configuration Controls Windows and Lookbacks
Given the organization sets its workweek start day to Sunday and workdays to Sunday–Thursday in the billing timezone When computing weekly billing windows, proration segments, and dormancy lookbacks Then all windows start at 00:00 Sunday in the billing timezone and include only configured workdays And changing the workweek setting updates subsequent calculations starting from the next workweek boundary in the billing timezone And existing closed invoice periods remain unchanged
Per-Organization Settings Defaults, Validation, and Effective Timing
Given a new organization without custom settings Then the billing timezone defaults to UTC and the workweek defaults to Monday–Friday And timezone values must be valid IANA TZ identifiers; invalid values are rejected with a clear error message And changes are persisted and audit-logged with editor, timestamp, and old/new values And after an admin updates the billing timezone or workweek, “right now” computations reflect the change within 60 seconds And invoice cutoffs and proration adopt the new settings starting at the next invoice boundary in the billing timezone
Role-Based Visibility & Data Scoping
"As an IT administrator, I want only authorized users to view the meter so that billing exposure data remains confidential."
Description

Access control that restricts the Active Series Meter to Finance and Admin roles, with tenant scoping so users only see data for their organization. Implements server-side authorization checks on the counter API, redacts links for unauthorized roles, and logs access for audit compliance. Supports SSO group mapping to roles.

Acceptance Criteria
Authorized Finance/Admin Access
Given an authenticated user with role Finance or Admin within tenant T When they navigate to the Active Series Meter Then the meter loads and displays counts and status badges for tenant T And no data from other tenants is displayed
Unauthorized User UI Redaction
Given an authenticated user without Finance/Admin role within tenant T When they view the main navigation or dashboard Then the Active Series Meter link and widgets are hidden or disabled And any in-page meter placeholder shows an "Insufficient permissions" message without revealing counts
Unauthorized Deep-Link and API Denial
Given an authenticated user without Finance/Admin role within tenant T When they access the meter deep-link URL or call the counter API Then the UI shows a 403 error page without meter data And the API responds with HTTP 403 and a JSON error body without any meter counts
Tenant Scoping Enforcement
Given an authenticated Finance/Admin user from tenant A When they attempt to request meter data for tenant B via URL, query, or header manipulation Then the API returns HTTP 403 with no data And the UI does not display any counts for tenant B And requests without an explicit tenant parameter are scoped to tenant A based on the authenticated context
Server-Side Authorization on Counter API
Given an unauthenticated request to the counter API When the request is processed Then the API returns HTTP 401 with WWW-Authenticate header Given an authenticated request with a non-Finance/Admin role When the request is processed Then the API returns HTTP 403 and no meter data Given an authenticated Finance/Admin request When the request is processed Then the API returns HTTP 200 with JSON containing counts and status badges for the caller’s tenant only And any client-supplied role or tenant headers are ignored for authorization decisions
Audit Logging of Access and Attempts
Given any attempt to view the meter UI or call the counter API When the attempt is processed (success or denial) Then an audit log entry is written with at least: timestamp, userId, tenantId, endpoint/action, outcome (success/denied), httpStatus, requesterIp, role And the entry is queryable in the audit log viewer within 60 seconds
SSO Group Mapping to Roles
Given a tenant admin configures IdP group-to-role mappings for Finance and Admin When a user logs in via SSO with group memberships that map to Finance or Admin Then the user is granted the corresponding role in the application for that session And the user can access the Active Series Meter accordingly Given the user’s IdP group membership is changed to remove Finance/Admin When the user starts a new session or refreshes tokens Then the role is removed and access to the meter is denied (UI redaction and 403 on API)
Threshold Alerts & Pre-charge Reminders
"As a finance lead, I want proactive alerts about rising active counts and impending billables so that I can take housekeeping actions before costs accrue."
Description

Configurable alerts that notify stakeholders when the number of Active series crosses defined thresholds or when Dormant-Pending series are projected to become billable within a set window. Supports email, Slack, and webhook channels, per-org configuration, quiet hours, and per-threshold recipients. Includes one-click actions in notifications to review or pause affected series and tracks acknowledgments.

Acceptance Criteria
Alert on Active Series Threshold Crossing
Given an org-level alert rule "Active >= 50" is configured with channels [Email, Slack, Webhook] and per-threshold recipients, and quiet hours are disabled When the Active series count transitions from below 50 to 50 or higher Then the system generates exactly one "threshold_crossed" event for that rule within 2 minutes And sends one notification per configured channel to the rule's recipients/endpoint And each notification includes org_id, threshold_id, previous_count, current_count, direction="up", UTC timestamp, and a deep link to Review Affected Series And the event and per-channel delivery attempts are recorded with a correlation_id in the audit log
Pre-charge Reminders for Dormant-Pending Series
Given an org has pre-charge reminders enabled with window = 72 hours and per-threshold recipients configured When the system projects one or more Dormant-Pending series will become billable within the next 72 hours Then a consolidated pre-charge reminder is sent to the configured recipients within 10 minutes And the reminder lists the count of impacted series and includes deep links to review them and a one-click "Pause All" action And series that are paused or already acknowledged for this window are excluded from the reminder And no duplicate reminder is sent for the same series unless the projection window or series status changes
Quiet Hours Deferral and Aggregation
Given org quiet hours are configured from 21:00–07:00 in the org's primary timezone When an alert or reminder would be triggered during quiet hours Then the notification is not delivered immediately and is queued And it is delivered within 5 minutes after quiet hours end And multiple queued notifications of the same type are aggregated into a single message per channel with combined counts and links And the deferral and aggregation actions are recorded in the audit log
Channel Delivery and Retry Across Email, Slack, and Webhook
Given valid channel configurations (verified email addresses, Slack destination, and webhook URL) When an alert event is generated Then delivery is attempted to each configured channel within 2 minutes And failures are retried up to 3 times with exponential backoff (1 min, 5 min, 15 min) And after final failure, the delivery is marked failed with a reason code and is visible in delivery logs And successful deliveries are not retried and do not create duplicate messages
One-click Review and Pause Actions
Given a notification includes "Review" and "Pause All" one-click actions for the affected series When an authorized recipient clicks "Review" Then the app opens to a filtered view listing the affected series and the action is logged When an authorized recipient clicks "Pause All" Then all targeted series are paused within 60 seconds, the Active count updates accordingly, and a confirmation is shown And if the recipient lacks permission, the action is blocked with an authorization error and no series are paused And one-click links are single-use, expire after 24 hours, and are invalidated after use
Acknowledgment Tracking and Reminder Suppression
Given a notification includes an "Acknowledge" action When a recipient acknowledges an alert or reminder Then the system records the acknowledgment with user_id, timestamp, and alert_id in the audit log And subsequent reminders for the same alert condition are suppressed until the condition resolves or changes And acknowledgment status is displayed in the alert's detail view for admins
Historical Snapshots & Reconciliation Export
"As a controller, I want historical meter data and exports so that I can reconcile month-end invoices and investigate anomalies."
Description

Persistent storage of meter snapshots and state-change events for audit and reconciliation. Captures at least hourly aggregates and on-change deltas with timestamps and status composition. Provides CSV export and read-only API endpoints filtered by date range and status, with pagination and rate limiting. Retains data for 12 months by default with configurable retention.

Acceptance Criteria
Hourly Snapshot Persistence
Given the Active Series Meter is operational, when inspecting stored aggregates over any continuous 24-hour period, then there is no gap greater than 60 minutes between consecutive aggregate snapshot timestamps. Given an aggregate snapshot is stored at time T, then its timestamp is UTC ISO 8601 with timezone Z and has millisecond precision. Given an aggregate snapshot at time T, then total_count equals active_count + paused_count + dormant_pending_count and all counts are non-negative integers.
On-Change Delta Event Capture
Given a recurring series changes status from S1 to S2, when the change is committed, then a delta event is stored within 60 seconds with fields: series_id, from_status=S1, to_status=S2, and event_timestamp in UTC ISO 8601. Given multiple status changes for the same series in a short interval, then each change produces exactly one delta event (no duplicates) and events are retrievable in timestamp-ascending order. Given no status changes occur during a period, then no delta events are stored for that period.
Status Taxonomy Enforcement
Given any snapshot or delta is stored, then each status value must be one of: Active, Paused, Dormant-Pending; writes containing other status values are rejected. Given an aggregate snapshot is emitted, then counts for all three statuses are present; missing statuses are represented as 0. Given a stored record, then all count fields are integers >= 0.
CSV Export: Date Range and Status Filters
Given a date range [from,to) and a status filter in {Active, Paused, Dormant-Pending, All}, when requesting CSV export, then the CSV contains only records with event_timestamp in [from,to) matching the filter. Given CSV export is requested, then the first row is a header and rows are UTF-8, comma-separated, and include columns: event_timestamp, record_type, total_count, active_count, paused_count, dormant_pending_count, series_id, from_status, to_status. Given no records match the filters, then the CSV contains only the header row and the response status is 200. Given records match, then rows are sorted by event_timestamp ascending.
Read-Only API: Filters, Pagination, and Rate Limiting
Given the snapshots/events API is called, then only GET and HEAD methods are allowed; POST, PUT, PATCH, DELETE return 405. Given valid query parameters (from, to, status, type, page[size], page[cursor]), when requesting data, then the response applies filters and returns paginated JSON; page[size] defaults to 100 and is capped at 1000. Given the request rate exceeds the configured limit, then the API responds 429 with Retry-After and X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers. Given multiple pages are retrieved following the provided next cursor, then no records are skipped or duplicated across those pages.
Data Retention Policy Enforcement
Given the default configuration, when querying for records older than 12 months from now, then no records are returned by API or CSV export. Given retention is reconfigured to R days, then records older than R days become unavailable (API/CSV) and are purged within 24 hours. Given retention is increased (e.g., from 180 to 365 days), then records newer than the new cutoff remain available and are not purged.
Aggregate–Delta Reconciliation Correctness
Given an initial aggregate snapshot at time T0 and all delta events in (T0, Tn], when applying the deltas sequentially to the T0 composition, then the computed composition equals the stored aggregate composition at each aggregate timestamp in (T0, Tn]. Given the same window, then the net change between the first and last aggregate per-status counts equals the sum of corresponding deltas over that window.
Inline Housekeeping Quick Actions
"As an admin, I want one-click housekeeping actions from the meter so that I can quickly reduce billable exposure without navigating away."
Description

Contextual actions adjacent to the meter and breakdown lists that let authorized users pause, resume, or archive a series in one click. Includes confirmation modals with impact summaries, optimistic UI updates synchronized with the counter service, and audit logging of actor, action, and reason. Honors role permissions and supports bulk actions for filtered results.

Acceptance Criteria
Pause Active Series via Inline Quick Action
Given I am a Finance or Admin user viewing an Active series in the meter or breakdown list When I click the Pause quick action for that series and confirm in the modal after entering a reason of at least 10 characters Then the series status badge changes to Paused within 300 ms (optimistic) And the Active count on the meter decreases by 1 within 300 ms (optimistic) And the change is persisted by the backend and reconciled with the counter service within 2 seconds And on success the optimistic state remains; on failure the UI reverts within 300 ms and a visible error toast is shown And an audit record is created containing actor ID, role, timestamp (UTC), series ID, prior status Active, new status Paused, reason text, and origin "Inline Quick Action"
Resume Paused Series via Inline Quick Action
Given I am a Finance or Admin user viewing a Paused series When I click the Resume quick action and confirm in the modal with a reason of at least 10 characters Then the series status badge changes to Active within 300 ms (optimistic) And the Active count on the meter increases by 1 within 300 ms (optimistic) And the backend update completes and reconciles with the counter service within 2 seconds And an audit record is created with actor ID, role, timestamp (UTC), series ID, prior status Paused, new status Active, reason, and origin "Inline Quick Action" And if the backend fails, the UI reverts the optimistic changes within 300 ms and shows a visible error toast
Archive Series from Breakdown List with Impact Summary
Given I am an Admin user viewing a series eligible for archive in the breakdown list When I click the Archive quick action and the confirmation modal appears with an irreversible warning and impact summary (series name, current status, next occurrence if any, and billing impact) And I provide a reason of at least 10 characters and confirm Then the series is removed from active/paused/dormant lists in the UI according to its prior status within 300 ms (optimistic) And if the series was Active, the meter Active count decreases by 1 within 300 ms (optimistic); otherwise the meter total remains unchanged And the backend archive completes and reconciles with the counter service within 2 seconds And an audit record is created with actor ID, role, timestamp (UTC), series ID, prior status, new status Archived, reason, and origin "Inline Quick Action" And on backend failure, the UI fully reverts and displays a visible error toast without leaving partial state
Bulk Pause on Filtered Active Series
Given I have filtered the breakdown list to Active series and selected one or more rows (including Select All for current filter) When I click Bulk Pause and the batch confirmation modal shows the number of selected series and the projected meter delta And I enter a single required reason of at least 10 characters to apply to all selected series and confirm Then the selected series statuses change to Paused within 500 ms (optimistic) and the meter Active count decreases by the number successfully applied (optimistic) And the backend processes each series; successes remain Paused and failures revert in the UI within 300 ms of receiving the error And a result summary shows counts of succeeded and failed series, with retry option for failures And an audit entry is created per series with actor, action Pause, reason, prior and new status, timestamp (UTC), and origin "Bulk Inline Quick Action" And the final meter value matches the counter service after reconciliation within 2 seconds
Permission Enforcement for Quick Actions
Given I am a Viewer (or any role without housekeeping permissions) viewing the meter and breakdown lists Then Pause, Resume, and Archive quick actions are not visible or are disabled with an explanatory tooltip When I attempt to invoke the underlying API directly for a quick action Then the request is rejected with HTTP 403 and no state change occurs And the meter and series statuses remain unchanged and no audit record is created
Audit Log Completeness and Accessibility
Given any quick action (Pause, Resume, Archive) is confirmed via modal Then an audit entry is recorded within 2 seconds and is retrievable via the audit log UI And each entry includes: actor ID, actor role, action, series ID, prior status, new status, reason (non-empty), timestamp in UTC, origin context (Meter vs Breakdown), and correlation ID And bulk actions create one audit entry per series affected with the same correlation ID And audit entries are immutable and visible only to Admin and Finance roles

Dormant Auto-Pause

Automatically pauses billing when a series stops firing for its cadence-defined threshold (e.g., two missed occurrences). Includes a configurable grace window and auto-resume on the next scheduled send. You stop paying for idle series without manual policing, while owners retain their setup and history.

Requirements

Cadence-Based Dormancy Detection
"As a workspace owner, I want the system to detect when a meeting series becomes dormant based on its cadence so that inactive series are identified without manual monitoring."
Description

Implement a rules engine that flags a series as dormant when it misses its cadence-defined threshold of occurrences (default: 2) without successful sends. Detection must be cadence-aware (weekly, biweekly, monthly, custom) and timezone-consistent, using the series’ canonical timezone and work-window definitions from TimeTether. Exclude intentional non-sends (e.g., owner-snoozed, company holidays, org-wide blackouts) and failed sends due to temporary platform outages within the grace window. The detector should run as an idempotent, retry-safe job, record reason codes for non-sends, and expose a clear state machine (Active → At Risk → Dormant). It must not affect fairness rotation or scheduling logic; it only evaluates send outcomes. Integrates with event telemetry and the scheduling service to distinguish “no slot found” versus “skipped by policy.” Outputs structured signals consumable by billing, notifications, and UI layers.

Acceptance Criteria
Dormancy Flag After Threshold Misses
Given a series with cadence (weekly, biweekly, monthly, or custom) and a dormancy threshold of N consecutive missed occurrences (default N=2) And no successful sends have occurred for the last N scheduled occurrences When the detector job runs Then the series state transitions to Dormant And a single dormancy_detected signal is emitted with series_id, previous_state=Active|At Risk, new_state=Dormant, cadence_type, missed_count=N, and detection_timestamp in the series canonical timezone And fairness rotation and scheduling logic remain unchanged
At-Risk State After First Miss
Given a series in Active state with threshold N>=2 And exactly 1 consecutive scheduled occurrence passed without a successful send (excluding excluded reasons) When the detector job runs Then the series state transitions to At Risk And missed_count=1 is persisted on the series And no billing pause signal is emitted yet
Exclusion of Intentional and Grace-Window Non-Sends
Given a series with a configured grace window (e.g., 48h) and threshold N And a scheduled occurrence results in a non-send due to owner-snoozed, company_holiday, org_blackout, or policy_skip When the detector job runs Then the occurrence is recorded with a reason_code and does not increment missed_count Given a scheduled occurrence fails due to platform_outage within the grace window When the detector job runs Then the occurrence is recorded with reason_code=platform_outage and does not increment missed_count Given a scheduled occurrence fails due to platform_outage outside the grace window When the detector job runs Then the occurrence increments missed_count by 1
Timezone-Consistent, Cadence-Aware Evaluation
Given a series with canonical_timezone TZ and defined work-window And scheduled occurrences are computed by cadence (weekly, biweekly, monthly, or custom) in TZ When the detector evaluates whether an occurrence was missed Then evaluation uses TZ boundary times and the series work-window to determine whether the occurrence has passed without a successful send And cross-day or month-boundary cases in TZ are handled without off-by-one errors
Idempotent and Retry-Safe Detector Execution
Given the detector job runs multiple times over the same evaluation window for a series with unchanged inputs When the job executes repeatedly or after a retry Then the series state and missed_count remain consistent and no duplicate dormancy_detected signals are emitted (signals are de-duplicated by deterministic idempotency key) And at-most-once state transitions are guaranteed per occurrence window
Reason Codes and Downstream Signals
Given a non-send occurs When the detector records the event Then it persists a reason_code in {owner_snoozed, company_holiday, org_blackout, policy_skip, no_slot_found, platform_outage, other} And integrates with scheduling service to distinguish no_slot_found vs policy_skip And emits a structured telemetry event with series_id, occurrence_id, reason_code, timestamp, and cadence_type consumable by billing, notifications, and UI
Auto-Resume and State Reset on Next Successful Send
Given a series in Dormant or At Risk state And the next scheduled occurrence results in a successful send When the detector job runs Then the series state transitions to Active And missed_count resets to 0 and At Risk flag clears And a single resume_detected signal is emitted with series_id, previous_state, new_state=Active And fairness rotation and scheduling logic remain unchanged
Configurable Grace Window
"As a team lead, I want to configure a grace window before auto-pause triggers so that occasional skips don’t pause billing prematurely."
Description

Provide workspace- and series-level settings to define a grace window before auto-pause triggers, configurable as number of missed occurrences and/or calendar duration (e.g., 2 occurrences or 14 days, whichever is later). Defaults are set at the workspace level with per-series overrides and policy locks by org admins. Validation must prevent contradictory settings and show the effective threshold in the series settings UI and API. The grace window must account for the series cadence and recognized holidays to reduce false positives. Changes to grace settings take effect prospectively and are versioned for audit. Expose settings via REST/GraphQL and Terraform-style config (if applicable) to support enterprise automation.

Acceptance Criteria
Workspace Defaults and Effective Threshold Display
Given a workspace default grace window of missedOccurrences=2 and calendarDays=14 with evaluationRule=later-of and no series-level override When a user opens any series settings UI in that workspace Then the UI displays "Grace window: later of 2 missed occurrences or 14 days" and shows the computed nextPauseEvaluationAt And GET /series/{seriesId}/settings returns effectiveGraceWindow { missedOccurrences: 2, calendarDays: 14, evaluationRule: "later-of", source: "workspace-default", locked: false } And a GraphQL query for series(id) returns matching effectiveGraceWindow fields And the effectiveGraceWindow values match the workspace defaults exactly
Series-Level Override With and Without Policy Lock
Given workspace defaults are set and policy lock is disabled When a series owner sets an override of missedOccurrences=3 and calendarDays=10 Then the save succeeds (HTTP 200/OK) and GET /series/{id}/settings shows source="series-override" with those values and updatedAt is within 2 seconds of the change And the UI indicates an override is active and displays the effective later-of 3 missed or 10 days Given an org admin enables policy lock at the workspace When a series owner attempts to modify the series grace window via UI, REST, or GraphQL Then the request is rejected with HTTP 403 and errorCode=POLICY_LOCKED and no changes persist And the series settings UI controls are disabled and annotated with a lock indicator
Validation Rules Prevent Contradictory Grace Settings
Given a request to set grace window values When missedOccurrences is provided and is < 1 Then the API responds 422 with errorCode=INVALID_GRACE_WINDOW and fieldErrors.missedOccurrences present When calendarDays is provided and is < 1 Then the API responds 422 with errorCode=INVALID_GRACE_WINDOW and fieldErrors.calendarDays present When both missedOccurrences and calendarDays are null or omitted Then the API responds 422 with message "At least one threshold is required" When only one threshold is provided Then evaluationRule is set implicitly to occurrences-only or days-only and save succeeds When both thresholds are provided Then evaluationRule must be later-of (default); attempts to set earlier-of or unknown rules are rejected with 422 errorCode=UNSUPPORTED_EVALUATION_RULE
Holiday and Cadence-Aware Grace Window Evaluation
Given a weekly series scheduled Mondays 09:00 anchored to the workspace timezone and the workspace has a recognized holiday on the next scheduled Monday When that Monday occurrence is not sent due to the holiday Then missedOccurrences for grace evaluation does not increment for that date and calendarDays toward the grace window do not accrue for that holiday date And the next evaluation uses the next non-holiday scheduled occurrence according to the series cadence Given a biweekly series When evaluating missedOccurrences and calendarDays Then counts and intervals are computed against the biweekly schedule (not weekly) And holiday recognition is determined solely by the workspace-configured holiday calendars and the series anchor timezone
Prospective Effect and Versioned Audit of Grace Settings
Given a series has accrued 1 missed occurrence under effective later-of 2 occurrences or 14 days When an admin changes the workspace defaults to later-of 3 occurrences or 21 days at T0 Then the series effectiveGraceWindow updates immediately but accrued historical counts are not recalculated; future evaluations use the new thresholds from the next scheduled occurrence after T0 And an audit record is created with version increment, previous and new values, actor, actorRole, channel (UI|API|TF), timestamp, entityId (workspace/series), and reason (optional) And GET /audit?entityType=series&entityId={id}&eventType=GRACE_WINDOW_CHANGE returns the new record And a GraphQL query auditLog(entityId: {id}, type: GRACE_WINDOW_CHANGE) returns matching details
API, GraphQL, and Terraform Configuration Support
Given enterprise automation via Terraform-style config for TimeTether When applying a configuration that sets workspace grace window missedOccurrences=2, calendarDays=14, evaluationRule=later-of, lockOverrides=true Then terraform plan shows the intended change and terraform apply succeeds; a subsequent apply is idempotent with 0 to change And if settings are manually changed outside Terraform, terraform plan detects drift and proposes corrections And REST endpoints (GET/PUT /workspaces/{id}/settings/grace-window and GET/PUT /series/{id}/settings/grace-window) enforce authZ and return/accept the defined schema And GraphQL exposes query/mutation types to read/update grace window with the same fields including effectiveGraceWindow and policyLock
Auto-Pause Trigger Uses Later-Of Threshold
Given a series with effective grace window later-of 2 missed occurrences or 14 days When the series misses 2 scheduled occurrences and only 10 days have elapsed since the first miss Then the series does not auto-pause When 14 days have elapsed since the first miss and the next evaluation tick runs Then the series auto-pauses and status transitions to PAUSED_DORMANT When only missedOccurrences=2 is configured and calendarDays is null Then the series auto-pauses immediately upon reaching 2 missed occurrences at the next evaluation When only calendarDays=14 is configured and missedOccurrences is null Then the series auto-pauses once 14 days have elapsed since the last successful send, regardless of the number of missed occurrences And the UI and APIs display the nextPauseEvaluationAt and the reason showing which threshold was satisfied
Billing Auto-Pause with Entitlement Preservation
"As a finance admin, I want billing to automatically pause for dormant series while retaining their setup and history so that we stop paying for idle series without losing configurations."
Description

When a series transitions to Dormant, automatically pause billing for that series while preserving all entitlements: configuration, history, analytics, fairness rotation state, and participant lists. Change the series billing state to Paused (Dormant) with an effective date at the end of the grace window; ensure idempotent updates and concurrency safety. Cease metered usage accrual and reflect the pause on invoices and usage reports with annotated line items. Do not count dormant series toward active-series limits while maintaining their slot in plan entitlements. Integrate with the billing ledger for reversals/pro-rations as per plan rules and emit domain events (series.billing.paused/resumed) for downstream systems. No destructive data actions are allowed during pause.

Acceptance Criteria
Dormancy Grace Window Effective Pause
Given a series transitions to Dormant on 2025-09-01T00:00:00Z with a 72-hour grace window When the grace window elapses Then the series.billing_state is "Paused (Dormant)" with effective_at = 2025-09-04T00:00:00Z And exactly one ledger pause entry exists with effective_at = 2025-09-04T00:00:00Z And subsequent evaluations do not create additional pause entries or change effective_at (idempotent) And GET /series/{id} reflects billing_state = "Paused (Dormant)" and effective_at = 2025-09-04T00:00:00Z
Metered Usage Cease and Invoice Annotation
Given the series is paused with effective_at = 2025-09-04T00:00:00Z within billing period 2025-09-01 to 2025-09-30 When generating the invoice and usage report for that period Then metered_usage for the series after 2025-09-04T00:00:00Z equals 0 units And charges before 2025-09-04T00:00:00Z are prorated per plan rules with corresponding ledger entries And the invoice includes an annotated line item indicating "Paused (Dormant)" and the effective date And the usage report excludes post-effective usage for the series and marks the series as dormant for the remaining period
Entitlement Preservation During Pause
Given the series has configuration C, history H, analytics A, fairness_rotation_state F, and participant_list P prior to pause When the series enters and remains in Paused (Dormant) state Then fetching these artifacts returns identical C, H, A, F, and P (no data loss or mutation) And no destructive data actions are performed by the pause process (no deletes, truncations, or resets) And fairness_rotation_state F is unchanged across the pause interval and available for immediate use upon resume
Active-Limit Interaction and Slot Reservation
Given the plan allows 5 active series and the account currently has 4 Active and 1 Paused (Dormant) When attempting to create an additional Active series Then the request is rejected with error code plan_limit_reached because the dormant series maintains a reserved slot And GET /limits returns active_count = 4, reserved_count = 1, limit = 5 When requesting resume of the dormant series in the same state Then resume succeeds without requiring plan upgrade and the account becomes active_count = 5, reserved_count = 0
Idempotent and Concurrency-Safe Transitions
Given two workers process a pause for the same series within 100ms using the same idempotency key When both requests are executed Then exactly one ledger pause entry exists and exactly one series.billing.paused event is emitted And both requests return a success response without creating duplicates When the identical pause request is replayed later with the same idempotency key Then no new ledger entries or events are produced and the response is idempotent When concurrent pause and resume requests race Then a single, deterministic final state is persisted based on last-write-wins with monotonic effective_at timestamps and no overlapping billing intervals
Auto-Resume on Next Scheduled Send
Given a series is Paused (Dormant) with effective_at = 2025-09-04T00:00:00Z and the next scheduled send is at 2025-09-10T15:00:00Z When the scheduled send is triggered Then the billing state transitions to "Active" with effective_at = 2025-09-10T15:00:00Z And metered usage accrual resumes from 2025-09-10T15:00:00Z And exactly one ledger resume entry is created and one series.billing.resumed event is emitted And retries of the resume are idempotent (no duplicate ledger entries or events)
Auto-Resume on Next Scheduled Send
"As a series owner, I want billing to automatically resume the moment a dormant series starts sending again so that I don’t need to manually manage billing states."
Description

Automatically lift the paused billing state when the dormant series successfully sends its next scheduled invite or is explicitly resumed by an authorized user/API. Resumption must be triggered only on real sends (exclude test sends and dry runs) and must be timezone-accurate. Update billing and entitlement states atomically and emit a series.billing.resumed event. Restore the series to Active without altering fairness rotation history or cadence rules. Handle edge cases: backfilled sends, manual reschedules, and cross-midnight cadence shifts. Provide safeguards against flapping via a short resumption stabilization window (e.g., require one successful send within the next cadence period).

Acceptance Criteria
Auto-Resume on Next Real Scheduled Send
Given a series is in Paused billing state due to dormancy and has a next occurrence scheduled in its homeTimezone And the occurrence is a production send (test=false, dryRun=false, backfill=false) When the occurrence is dispatched and recorded as delivered=success Then update billingState=Active and entitlementState=Enabled in a single atomic transaction And emit event series.billing.resumed with fields {seriesId, triggerType=scheduled_send, resumedBy=system, occurredAt=ISO-8601 with timezone, previousState=Paused, billingCycleId} And do not modify fairness rotation history or cadence configuration And persist an audit record linking sendId to the resume event via correlationId
Explicit Resume via Authorized User or API
Given a series is in Paused billing state And an actor attempts to resume the series When the actor is authorized (user role Owner/Admin or API token with scope series.resume) Then set billingState=Active and entitlementState=Enabled atomically And emit series.billing.resumed with triggerType=manual and resumedBy set to the actorId/clientId And return 200 with the updated series status When the actor is unauthorized Then return 403, make no state changes, and emit no events
Ignore Test, Dry-Run, and Backfilled Sends for Auto-Resume
Given a series is in Paused billing state When a send executes with test=true or dryRun=true Then do not resume billing, do not emit series.billing.resumed, and leave entitlements unchanged When a send executes with backfill=true for a previously missed occurrence Then do not auto-resume; only the next on-schedule production send (test=false, dryRun=false, backfill=false) may trigger resumption
Manual Reschedule and Cross-Midnight Cadence Handling
Given a series is in Paused billing state with a next scheduled occurrence in its homeTimezone And the owner manually reschedules that occurrence to a new time that may cross midnight in the homeTimezone When the rescheduled occurrence is sent successfully (test=false, dryRun=false, backfill=false) Then set billingState=Active and entitlementState=Enabled atomically And emit series.billing.resumed with triggerType=scheduled_send and resumedBy=system, using occurredAt in the series homeTimezone And compute cadence period boundaries across midnight correctly for downstream evaluations And do not alter fairness rotation history or cadence rules
Atomicity, Idempotency, and Error Handling on Resume
Given resumption is triggered by a send-success signal that may be retried When duplicate notifications referencing the same sendId are received Then the resume operation is idempotent: exactly one state transition to Active and at most one series.billing.resumed event for that sendId When any part of the transaction (state update or event emission) fails Then roll back all changes, emit no event, and surface a retriable error for reprocessing And all updates to billing and entitlements occur within a single atomic transaction
Resumption Stabilization Window (Anti-Flapping)
Given a series auto-resumes to Active on a successful production send in its homeTimezone When no additional successful production send occurs within the next full cadence period measured from the resume timestamp Then automatically return the series to Paused at the end of that cadence period and emit series.billing.repaused with reason=stabilization_timeout When at least one additional successful production send occurs within that cadence period Then keep the series Active beyond the stabilization window and emit series.billing.stabilized And in all stabilization outcomes, do not modify fairness rotation history or cadence rules
Owner Notifications and Activity Feed
"As a series owner, I want timely notifications about impending auto-pause and resumption so that I can intervene if needed and maintain awareness."
Description

Notify series owners and relevant stakeholders when a series enters At Risk, transitions to Dormant (billing paused), and when it resumes. Support email, in-app, and Slack notifications with actionable content: reason for status, remaining grace window, one-click Resume/Exempt links, and a link to review series health. Apply rate limiting, localization, and user preferences. Log all state changes and actions to the series activity feed with timestamps, actor, and reason codes. Provide pre-send reminders as the grace window nears expiration and emit webhook callbacks for external automation.

Acceptance Criteria
State Transition Notifications
- Given a series misses scheduled occurrences and meets the configured At Risk condition, When the state changes to At Risk, Then notify all series owners and configured stakeholders within 60 seconds via each recipient’s enabled channels. - Given the series continues to miss until the configured Dormant threshold is reached, When the state changes to Dormant, Then notify owners and stakeholders within 60 seconds and indicate that billing has been paused. - Given a Dormant series successfully fires on its next scheduled send, When the system detects the successful send, Then notify owners and stakeholders within 60 seconds that the series has resumed and billing will resume. - Rule: Recipients are limited to series owners and explicitly configured stakeholders for the series; no other users are notified. - Rule: Each state transition generates at most one notification per recipient (idempotent by series_id + transition_id).
Actionable Multichannel Notification Content
- Rule: Email, in-app, and Slack notifications include: current state (At Risk/Dormant/Resumed), reason code (e.g., missed_occurrence_count), remaining grace window (relative and absolute timestamp), link to Review Series Health, and one-click actions: Resume and Exempt. - Rule: One-click Resume/Exempt actions use signed, single-use tokens that expire after 24 hours and are scoped to the recipient and series; unauthorized or expired attempts return 401 and do not alter state. - Rule: Slack uses interactive buttons; in-app displays an actionable banner; email contains buttons/links with equivalent actions. - Rule: All action and health links deep-link to the correct series context and carry a correlation_id for auditability.
Notification Rate Limiting and De-duplication
- Rule: Per recipient per series, send no more than 1 notification per unique state transition; suppress duplicates for 24 hours. - Rule: Apply a workspace-level cap of 100 notifications per rolling hour; excess notifications are queued FIFO for up to 6 hours, then dropped with a logged reason if undelivered. - Rule: Hard de-duplication key = (series_id, recipient_id, transition_id); suppressed notifications are recorded with reason_code "rate_limited". - Rule: Reminder notifications are excluded from the transition cap but limited to 1 per grace window per recipient and are independently de-duplicated.
Localization and User Preferences Enforcement
- Rule: Notification language matches the recipient’s preferred locale; if missing/unavailable, fallback to the workspace default locale and record the selected locale in metadata. - Rule: All timestamps in notification bodies are rendered in the recipient’s configured timezone and include ISO 8601 in metadata. - Rule: Channel-level preferences are honored; disabled channels are not attempted. - Rule: On permanent channel failure (e.g., Slack user not found), fall back to the next enabled channel and log the fallback path.
Grace Window Expiry Reminders
- Given a series is At Risk and has a remaining grace window, When the remaining time reaches the configured reminder threshold (default 24 hours), Then send a reminder to owners and stakeholders via each recipient’s enabled channels. - Rule: The reminder includes remaining time, next scheduled occurrence window, and one-click Resume/Exempt actions plus a link to Review Series Health. - Rule: Automatically cancel scheduled reminders if the series resumes or transitions to Dormant before the reminder time. - Rule: Send at most 1 reminder per grace window per recipient; enforce de-duplication by (series_id, recipient_id, grace_window_id).
Activity Feed Logging and Audit
- Rule: Every state change and user action (Resume/Exempt) appends an immutable feed entry capturing: timestamp (UTC), actor (user_id/system), from_state, to_state, reason_code, and correlation_id. - Rule: Feed entries include references to related notification delivery ids and webhook event ids when available. - Rule: The feed is append-only; corrections are recorded as new entries with reason_code "correction" and link to the superseded entry. - Rule: The activity feed is paginated (page size 50), sorted by occurred_at descending, and filterable by event type and date range.
Webhooks for External Automation
- Rule: Emit webhooks for events: at_risk.entered, dormant.entered, series.resumed, reminder.sent, action.resume.clicked, action.exempt.clicked. - Rule: Payload includes: event_type, event_id (UUIDv4), series_id, workspace_id, previous_state, new_state, reason_code, occurred_at (ISO 8601), recipient_ids (for notifications), and correlation_id. - Rule: Sign each webhook with HMAC-SHA256 using a per-workspace secret; include X-TimeTether-Signature and X-TimeTether-Event-Id headers. - Rule: Retry on non-2xx with exponential backoff for up to 24 hours with a maximum of 8 attempts; deliveries are idempotent by event_id.
Admin Overrides and Audit Logging
"As an org admin, I want override controls and full audit logs for auto-pause events so that we can meet governance requirements and correct mistakes."
Description

Provide admin controls to exempt specific series from auto-pause, adjust dormancy thresholds, and manually pause/resume with required reason codes. Enforce RBAC so only org admins or billing admins can perform overrides. Record a complete audit trail for detection signals, configuration changes, and state transitions, including actor, timestamp, old/new values, and correlation IDs. Expose audit logs in-app with filters and export via API/CSV to meet compliance requirements. Ensure data integrity with write-ahead logging and immutable history, and surface override status in billing reports and UI badges.

Acceptance Criteria
RBAC Enforcement for Admin Overrides
- Given a user without org admin or billing admin role, when they attempt to exempt a series, adjust dormancy thresholds, or manually pause/resume a series, then the request is rejected with HTTP 403 and no state change occurs. - And an audit log entry is created capturing actor, attempted action, target resource IDs, timestamp, and failure reason "insufficient_permissions" with correlation_id. - Given a user with org admin or billing admin role, when they perform any override action, then the action succeeds and is reflected within 5 seconds in UI and API. - And an audit log entry is created capturing actor, action, target resource IDs, timestamp, old/new values, correlation_id, and outcome "success".
Exempt Series from Auto-Pause
- Given an admin sets a series to "Exempt from auto-pause," when the series surpasses its dormancy threshold, then no auto-pause is applied and billing continues. - And a detection-signal log entry is recorded, and an exemption configuration-change log entry is recorded with old/new values and actor. - And the series shows an "Exempt" badge on list and detail views within 2 seconds, and the exemption appears in billing reports generated for the period. - Given the admin removes the exemption, when the next detection cycle runs, then the series is subject to auto-pause rules again.
Manual Pause/Resume with Reason Codes
- Given an admin initiates a manual pause, when no reason code from the allowed set is provided, then the action is blocked with validation error and no state change occurs. - Given an admin initiates a manual pause with a valid reason code, then the series enters "Paused (Manual)" state within 60 seconds; billing is suspended from that timestamp onward. - And an audit log entry records action "manual_pause," actor, timestamp, reason_code, old/new state, and correlation_id. - Given a series is in "Paused (Manual)" state, when a scheduled send occurs, then the series remains paused (no auto-resume) until an admin performs "manual_resume." - Given an admin performs "manual_resume" with a required reason code, then the series returns to "Active" before the next scheduled send, and billing resumes; audit log is recorded.
Adjust Dormancy Thresholds
- Given an admin updates a per-series dormancy threshold to an integer between 1 and 10 missed occurrences (inclusive), when the change is saved, then the effective threshold is updated and returned by the API. - Values outside 1–10 are rejected with validation error; no change occurs and an audit log entry is created with outcome "failure". - The system re-evaluates the series' current dormancy counter against the new threshold within 2 minutes; if the threshold is crossed, an appropriate state transition (auto-pause) is triggered and logged with a shared correlation_id linking detection and transition. - All threshold changes (org default and per-series) log actor, timestamp, old/new values, and correlation_id; only org/billing admins can perform updates.
Comprehensive Audit Logging and Integrity
- For each detection signal, configuration change, and state transition, the system records an immutable audit log entry with fields: event_id, timestamp (ISO 8601 UTC), actor_type/id, org_id, series_id, event_type, old_values, new_values, outcome, correlation_id, request_id. - Audit logs are append-only: API/UI do not support update or delete; attempts return HTTP 405/403 and are themselves logged. - Write-ahead logging ensures atomicity: for any state change, either both the change and its log entry commit, or neither; simulated crash during commit does not produce partial state. - Audit log entries include a monotonic sequence number per series to support ordering; retrieval returns events sorted by timestamp then sequence.
In-App Audit Log Filtering and Export
- The Audit Logs UI supports filtering by date range (UTC), actor, event type, org_id, series_id, outcome, and correlation_id; applying filters returns results within 2 seconds for up to 10,000 matching events. - Results are paginated (page size 100 default, up to 1,000) and sortable by timestamp asc/desc. - Users with view permissions can export the current filtered result set to CSV with defined headers and UTF-8 encoding; file downloads within 5 seconds for up to 50,000 events. - An API endpoint /v1/audit-logs supports authenticated export in JSON (default) and CSV (format=csv), with cursor pagination and filters matching the UI; rate limit 60 req/min; responses include a checksum and total count estimate.
Billing Reports and UI Badges Reflect Overrides
- Series with "Exempt from auto-pause" display a persistent "Exempt" badge in list and detail views with tooltip explaining billing implications; status appears in series API payload (override_status="exempt"). - Series in "Paused (Manual)" display a "Paused (Manual)" badge with reason code in tooltip and in API payload; they incur $0 charges for the paused period in billing reports. - Billing reports include columns: series_id, series_name, override_status, pause_type (auto|manual), pause_reason_code, paused_start/end, billable_days; totals reconcile to invoice amounts within 0.5%. - Any change to override status is reflected in the next billing report generation and in UI within 5 minutes; corresponding audit entries link to the report via correlation_id.

Smart Proration

Charges align precisely to usage when series start, stop, or change mid-cycle. TimeTether prorates automatically and issues retro-credits for early cancellations, so budgets reflect actual activity—not calendar intentions. Finance gets clean, fair invoices with fewer disputes and adjustments.

Requirements

Proration Calculation Engine
"As a finance admin, I want charges to be prorated automatically when series start, stop, or change mid-cycle so that invoices reflect actual usage and reduce manual adjustments."
Description

Implements a timezone-aware engine that calculates prorated charges for meeting series that start, stop, or change mid-billing-cycle. Uses configurable formulas to compute proration factors based on active service windows (activation to end-of-cycle), quantity (seats or series), and price book. Supports per-seat and per-series pricing, mid-cycle quantity changes, leap years/DST, multi-currency rounding rules, and customer billing anchors. Ensures charges align precisely to actual usage and produces deterministic results for the same inputs.

Acceptance Criteria
Mid-Cycle Activation Proration (Billing Anchor + Timezone)
Given a customer with a monthly billing anchor at 2025-09-01T09:00:00 America/Los_Angeles (cycle window [2025-09-01T09:00:00-07:00, 2025-10-01T09:00:00-07:00)) And a price book entry price_id=P123, unit_price=100.00, currency=USD, pricing_model=per_series And a series with quantity=1 that activates at 2025-09-10T13:30:00Z When the engine calculates the prorated charge for the September cycle Then it derives cycle boundaries from the customer billing anchor in the customer billing timezone And it sets the active service window to [activation_time, cycle_end) And it computes proration_factor = active_seconds / cycle_seconds with precision >= 8 decimal places And it computes amount = unit_price * quantity * proration_factor And it applies currency rounding rules after multiplication (see rounding criteria) And it outputs a deterministic charge line containing: price_id, quantity, currency, unit_price, proration_factor, window_start, window_end, amount
Early Cancellation Retro-Credit
Given a series active in a current billing cycle with cycle window [cycle_start, cycle_end) And the series is canceled at cancellation_time where cycle_start <= cancellation_time < cycle_end When the engine calculates adjustments for the current cycle Then it sets the unused service window to [cancellation_time, cycle_end) And it computes credit_proration_factor = unused_seconds / cycle_seconds with precision >= 8 decimal places And it computes credit_amount = -(unit_price * quantity * credit_proration_factor) And it applies currency rounding rules after multiplication And it emits a single credit line (negative amount) with fields: price_id, quantity, currency, unit_price, proration_factor, window_start, window_end, amount And if cancellation_time == activation_time within the cycle, the credit equals the full original charge for that cycle
Mid-Cycle Quantity Change (Seats/Series)
Given an active subscription with pricing_model=per_seat, unit_price=12.00 USD, and seat quantity changes within a single cycle And seat quantity timeline: 10 seats from cycle_start to T1, 15 seats from T1 to T2, 12 seats from T2 to cycle_end (cycle_start < T1 < T2 < cycle_end) When the engine calculates charges for the cycle Then it segments the cycle into non-overlapping windows: [cycle_start, T1), [T1, T2), [T2, cycle_end) And for each segment it computes proration_factor = segment_seconds / cycle_seconds And segment_amount = unit_price * seats_in_segment * proration_factor And the total cycle amount equals the sum of segment_amounts after applying currency rounding per segment And if seats decrease (e.g., 15 to 12 at T2), the engine computes a negative adjustment for the delta when modeled as a net change policy or computes lower forward charges when modeled as segment totals; in either configuration, the net for the cycle matches the piecewise calculation
Pricing Model Interpretation (Per-Seat vs Per-Series)
Rule: If pricing_model = per_seat, quantity = active_seat_count for the computed segment window Rule: If pricing_model = per_series, quantity = active_series_count for the computed segment window Rule: The engine selects the unit_price from the price book that matches the pricing_model and currency; if no exact match, it fails validation with a descriptive error Rule: Quantity changes mid-cycle are handled by segmenting the cycle at each change timestamp; each segment uses the correct quantity for that window Rule: If quantity = 0 for the entire cycle, no charge line is emitted for that price_id Rule: All emitted lines include price_id, pricing_model, quantity, currency, unit_price, proration_factor, window_start, window_end, amount
Leap Year and DST Boundary Handling
Given cycle boundaries are computed from the customer billing anchor in the customer billing timezone Rule: The engine computes proration using absolute seconds between timestamps so that leap days (Feb 29) and DST transitions (23- or 25-hour days) are accurately reflected Rule: For a cycle that includes Feb 29, cycle_seconds includes the additional day’s seconds; proration_factor uses actual seconds, not a fixed 30/31-day approximation Rule: For a segment that spans a DST spring-forward (short) day or fall-back (long) day, proration_factor reflects the actual 23- or 25-hour duration Rule: Window boundaries are half-open [start, end) to avoid double-counting boundary seconds
Multi-Currency Rounding Rules
Rule: Amount rounding occurs after amount = unit_price * quantity * proration_factor, not before Rule: Currency minor units are honored per configuration (e.g., USD=2, EUR=2, JPY=0); default rounding mode is half-up unless overridden by currency-specific configuration Rule: Proration factor precision is >= 8 decimal places; unit_price precision is taken from the price book; intermediate computations preserve at least 8 decimal places Rule: The engine returns both rounded amount and an internal unrounded amount used only for audit/tracing; only the rounded amount is billable Example: USD: unit_price=100.00, quantity=1, proration_factor=0.33333333 -> computed=33.333333 -> rounded=33.33 Example: JPY: unit_price=¥1000, quantity=1, proration_factor=0.33333333 -> computed=333.33333 -> rounded=¥333
Deterministic Results and Idempotency
Rule: For identical inputs (including customer_id, billing_anchor, timezone, cycle_start/end, activation/cancellation timestamps, quantity timeline, pricing_model, price_id, unit_price, currency, rounding rules, and configuration version), the engine produces byte-for-byte identical outputs (values and ordering) Rule: Segment ordering is deterministic: primary sort by window_start asc, secondary by price_id asc Rule: Numeric outputs use fixed precision and rounding rules to avoid non-deterministic floating-point variations; proration_factor string representation is normalized to 8 decimal places Rule: Re-running the engine with the same idempotency_key returns the same set of lines and does not create duplicates
Retro-Credit Automation
"As an accounting manager, I want unused time to be credited automatically on cancellation so that customers receive fair refunds and we avoid disputed charges."
Description

Automatically generates credits for unused portions when a series is canceled or downgraded mid-cycle. Applies credits to the next invoice or triggers immediate refunds per policy thresholds, with caps to prevent over-crediting. Creates compliant credit notes, supports multi-currency conversions at the correct FX rate timestamp, and updates account balance in real time. Integrates with payment gateways to post credit notes and sync status back to TimeTether.

Acceptance Criteria
Mid-Cycle Series Cancellation Credit Generation
Given an active billed meeting series with remaining time in the current billing cycle and a completed payment on the original invoice When the series is canceled effective immediately or on a specified date within the current cycle Then the system calculates a credit equal to the unconsumed portion of the billed charges for the remainder of the cycle, prorated to the minute according to the account time zone and proration policy And the credit includes proportional taxes and discounts from the original invoice line items And a credit note is generated within 5 seconds of cancellation with a reference to the original invoice and itemized lines And the account balance reflects the credit in real time (≤2 seconds) and the event is recorded in the audit log with actor, timestamp, and amount And the total credited amount for the series does not exceed the original cycle’s billable amount minus already-consumed usage
Downgrade Proration and Credit Application
Given an active series is downgraded (plan, seats, or add-ons) mid-cycle When the downgrade is saved Then the system computes the delta between original and downgraded charges for the remaining period and creates a credit for that delta And the credit is automatically applied to the next invoice unless refund policy thresholds are met And the next invoice shows an "Auto-applied credit" line item reducing the amount due; any unused remainder carries forward And the account balance and available credit wallet update within 2 seconds
Refund Threshold Policy Enforcement
Given a configurable refund_threshold and refund_window policy When a credit is created that meets or exceeds refund_threshold and falls within refund_window Then the system triggers an immediate refund to the original payment method via the configured gateway And the refund is submitted with an idempotency key and recorded with gateway transaction ID And the credit note and refund status are linked; on success, the customer balance is reduced accordingly; on failure, the credit remains unapplied and is retried per policy And customers are notified via email with refund details within 1 minute
Credit Cap Safeguard
Given a set of one or more credits related to the same original invoice When calculating any new credit for cancellation or downgrade Then the system enforces a hard cap so that cumulative credits do not exceed the maximum refundable amount for that invoice (including taxes and discounts as applicable) And attempts that would exceed the cap are blocked, logged, and surfaced with an error code and user-facing message
Credit Note Compliance and Numbering
Given credit notes must be jurisdiction-compliant When a credit note is issued Then it contains sequential credit note numbering, seller and buyer details, original invoice reference, line-item breakdown, tax rates and amounts, currency, FX rate and source (if applicable), issue timestamp, reason code, and authorized issuer And it is immutable after issuance except via a reversal credit note And a PDF/HTML copy is generated and accessible via secure link; the document is retained per retention policy
Multi-Currency Credit with FX Timestamping
Given the account currency differs from the settlement currency When a credit is created Then the conversion uses the FX spot rate at the event timestamp (cancellation/downgrade effective time) from the configured provider And the credit note displays amounts in both currencies, the rate, the provider, and timestamp And rounding follows ISO currency minor units; any residuals are posted to FX gain/loss ledger accounts
Payment Gateway Posting and Sync Back
Given configured payment gateways that support credit notes and refunds When a credit or refund is posted Then the system sends the request with idempotency, receives gateway acknowledgment, and stores gateway IDs and status And webhook callbacks update the TimeTether record to the final status within 10 minutes; failed posts are retried up to 6 times with exponential backoff starting at 1 minute And any status drift between TimeTether and gateway is detected daily and reconciled; discrepancies are surfaced in an operations dashboard
Mid-Cycle Upgrade/Downgrade Handling
"As a workspace admin, I want upgrades to bill immediately and downgrades to credit the remainder so that costs match the team’s current needs without billing surprises."
Description

Supports seamless plan changes within a billing cycle: immediate prorated charges for upgrades; deferred credits for downgrades. Maintains billing anchor dates, enforces configurable guardrails (minimum charge, change frequency limits), and handles quantity changes (e.g., seats added/removed). Produces consistent line items and balances across consecutive invoices and prevents double-billing via idempotent change events.

Acceptance Criteria
Mid-Cycle Upgrade Proration with Immediate Charge
Given a subscription on Plan A with a monthly billing anchor date When the user upgrades to Plan B effective immediately at time T within the cycle Then the system calculates a prorated charge equal to (Plan B price − Plan A price) × (seconds remaining in cycle ÷ total seconds in cycle), rounded to the currency minor unit, charges the payment method immediately, and updates the subscription to Plan B And Then the current-cycle invoice shows separate line items for the Plan A unused credit and the Plan B prorated charge covering [T, anchor), with a net total equal to the computed proration And Then the user receives a payment receipt within 1 minute of successful charge And If the immediate charge fails, Then the upgrade is not applied, no balance change occurs, and the user is notified with retry options
Mid-Cycle Downgrade with Deferred Credit
Given a subscription on Plan B When the user downgrades to Plan A at time T within the billing cycle Then the plan change is applied immediately, no immediate refund is issued, and a credit equal to (Plan B price − Plan A price) × (seconds remaining ÷ total seconds) is posted as a line item on the next invoice at the anchor date And Then the next invoice shows the credit line item referencing the change event ID and the period [T, anchor) And If the next invoice total after applying the credit is negative, Then the negative balance is carried forward to the account balance rather than auto-refunded
Billing Anchor Date Preservation Across Changes
Given any mid-cycle plan or quantity change When the change is applied Then the subscription's billing anchor date and interval remain unchanged, and the next invoice date is unaffected And Then no proration logic extends or shortens the billing cycle; all financial effects are contained within the current and next invoice only
Quantity Change Proration for Seats Added and Removed
Given a subscription priced per seat When N seats are added at time T within the cycle Then the immediate charge equals N × seat_price × (seconds remaining ÷ total seconds), rounded to the currency minor unit, and a receipt is sent And When M seats are removed at time U within the cycle Then no immediate refund is issued and a credit equal to M × seat_price × (seconds remaining from U ÷ total seconds) is posted on the next invoice And Then invoice line items explicitly denote quantities, unit price, period bounds, and change event IDs for added and removed seats
Guardrails — Minimum Charge and Change Frequency Limits
Given guardrails minimum_charge = X and max_changes_per_cycle = Y When an upgrade's computed immediate proration is less than X Then the change is scheduled to take effect at the next anchor date with zero immediate charge and the user is notified And When the number of plan/quantity changes requested in the current cycle exceeds Y Then the request is rejected with error code CHANGE_LIMIT_EXCEEDED, no financial impact occurs, and an audit trail entry is recorded
Idempotent Change Event Processing
Given a change request submitted with idempotency_key K When the same request is submitted multiple times within 24 hours Then exactly one subscription mutation and one financial impact occur; subsequent requests return the original result without duplicating charges or line items And Then all related invoice line items include a reference to idempotency_key K for traceability
Cross-Invoice Line Item Consistency and Balance Reconciliation
Given a subscription with one or more mid-cycle changes during a billing period When the next invoice is generated at the anchor date Then the sum of all prior prorated charges and deferred credits for the period equals the expected net delta from price/quantity changes within ±0.01 in the billing currency And Then each proration/credit line item references the originating change event ID, covers a non-overlapping window, and the union of windows equals [cycle_start, cycle_end) And Then account_balance_before + invoice_total − payments_since_previous_invoice = account_balance_after within rounding tolerance
Transparent Proration Line Items
"As a finance reviewer, I want proration details visible on invoices so that I can audit charges quickly and address customer questions with confidence."
Description

Generates clear invoice line items that itemize proration adjustments, including service dates, unit price, quantity, proration factor, subtotal, credits, and applicable taxes. Groups adjustments by meeting series and references series IDs/titles for traceability. Ensures totals reconcile to account balance and provides deep links to calculation details to reduce billing disputes.

Acceptance Criteria
Itemized Proration Fields on Invoice
Given an invoice is generated for an account with at least one prorated adjustment in the billing cycle When the invoice is rendered in the UI or exported (PDF/CSV) Then each proration line item displays: Service Start Date, Service End Date, Unit Price, Quantity, Proration Factor, Subtotal, Credit (if applicable), Tax Amount And Service Start/End Dates are formatted as YYYY-MM-DD And Unit Price, Subtotal, Credit, and Tax Amount are shown in the account currency with 2 decimal places using round half up And Quantity supports up to 3 decimal places And Proration Factor is a decimal between 0 and 1 inclusive with up to 4 decimal places And Subtotal equals Unit Price × Quantity × Proration Factor within 0.01 currency units
Group Adjustments by Meeting Series
Given multiple proration adjustments belong to the same meeting series within a billing cycle When the invoice is generated Then those line items are grouped under a single series section showing Series ID and Series Title And each line item includes a series_id that matches the section header And a group subtotal per series is displayed and equals the sum of its line item subtotals within 0.01 And grouping does not alter individual line item calculations or tax amounts
Invoice Totals Reconcile to Account Balance
Given an invoice with proration charges and credits When totals are calculated Then Invoice Subtotal equals the sum of all line item subtotals within 0.01 And Total Tax equals the sum of all line item tax amounts within 0.01 And Invoice Total equals Invoice Subtotal + Total Tax within 0.01 And New Account Balance equals Prior Balance + Invoice Total − Payments/Adjustments within 0.01 And a Reconciliation section displays these values and equations
Deep Links to Calculation Details
Given a proration line item on an invoice When the user clicks the "View Calculation" deep link Then the user is taken to a calculation detail view containing invoice_id, line_item_id, and series_id in the URL And the detail view shows: base unit price, billing period dates, billable days/units, proration factor formula and value, intermediate rounding steps, subtotal, tax jurisdiction and rate And access is restricted to users with Billing Viewer or Billing Admin roles; unauthorized users receive HTTP 403 And the deep link remains valid and accessible for at least 13 months from the invoice date
Retro-Credit for Early Series Cancellation
Given a meeting series is cancelled effective before the end of the current billing period When the next invoice is generated after the cancellation Then a proration credit line item is created with a negative subtotal equal to the unused portion based on the proration factor within 0.01 And the credit line item references the Series ID and Series Title of the cancelled series And the tax amount on the credit reverses the original tax proportionally according to the jurisdiction rules And the credit is dated with Service Start/End Dates reflecting the unused period
Tax Calculation on Prorated Amounts
Given an account’s tax jurisdiction and exemption status are known When calculating taxes for proration line items Then tax is applied to the prorated subtotal (after credits/discounts) using the correct jurisdiction rate And the line-level tax rate (%) and tax amount are displayed And tax-exempt accounts show 0.00 tax and include the exemption code on the line item And the sum of line-level tax amounts equals the invoice Total Tax within 0.01
Proration Preview & Simulation
"As a billing operator, I want to simulate proration before applying it so that I can validate outcomes and avoid issuing corrective adjustments later."
Description

Provides an admin UI and API to preview proration outcomes before committing changes. Supports what-if scenarios (date changes, seat adjustments, plan switches), displays deltas to the current invoice, and allows CSV export. Includes RBAC controls, a safe-apply mode with rollback, and guardrails that highlight policy violations or edge cases.

Acceptance Criteria
Plan Switch Mid-Cycle Proration Preview
Given a subscription on Plan A billed monthly with an open invoice When an admin previews switching to Plan B effective on a date within the current cycle Then the preview returns line-item charges and credits prorated by exact time fraction used And the UI displays delta versus the current invoice for subtotal, tax, and total And amounts are rounded to currency minor units and line-items sum to the displayed totals And no changes are persisted until apply is explicitly invoked And the preview renders within 2 seconds for subscriptions up to 5,000 seats
Seat Increase/Decrease Simulation With Policy Checks
Given a subscription with current seats N and contracted minimum seats M When an admin simulates an increase to N2 seats effective date D within the current cycle Then the preview shows an incremental prorated charge for quantity (N2−N) from D to period end and updates the delta totals When the admin simulates a decrease to N3 seats effective D Then the preview shows a prorated credit for quantity (N−N3) and updates the delta totals And if N3 < M, the preview blocks safe-apply and surfaces a guardrail message "Below minimum seats" with severity "blocking"
Early Cancellation Retro-Credit Preview
Given a subscription with a requested cancellation effective date before the period end When an admin previews the cancellation Then the preview shows a retro-credit for unused time as a negative line-item with the calculation window explicitly listed (start/end timestamps) And the delta total is negative and matches the sum of credit line-items within 0.01 of currency And no credit memo is generated until safe-apply is confirmed
CSV Export of Preview Results
Given a proration preview result is displayed When the user selects Export CSV Then a CSV downloads within 5 seconds containing headers: simulation_id, subscription_id, change_type, effective_date, start_ts, end_ts, plan, unit_price, quantity_before, quantity_after, proration_ratio, charge_amount, credit_amount, tax_amount, delta_total, currency, generated_at And numeric amounts are rounded to currency minor units and match on-screen totals within 0.01 And datetimes are ISO 8601 with timezone offsets And the file name includes simulation_id and generated_at timestamp
RBAC Enforcement For Preview And Safe-Apply
Given a user with role in {Finance Admin, Billing Analyst} When they access the Proration Preview UI or API Then access is allowed and actions are enabled Given a user with a role not in the allowed set When they attempt to access preview or export Then the UI disables actions and the API returns 403 with code "insufficient_permissions" When an authorized user invokes safe-apply Then the system requires confirmation, applies the changes idempotently using simulation_id, and on any partial failure performs a full rollback to the prior invoice state And an audit log entry is recorded with actor, action, simulation_id, before/after summary, timestamp, and correlation_id
Preview API Contract And Idempotency
Given POST /api/v1/proration/preview with payload {subscription_id, proposed_changes[], effective_date, timezone} When the request is valid Then respond 200 with body containing {simulation_id, currency, rounding, delta_totals {subtotal, tax, total}, line_items[], warnings[], calculation_window, expires_at} and do not persist changes And include headers X-Correlation-Id and Cache-Control: max-age=900, private When the same request (same payload) is repeated within 15 minutes Then the same simulation_id and identical results are returned When input is invalid (e.g., effective_date outside current cycle, negative seat count, incompatible plan) Then respond 422 with per-field errors {field, code, message} And P95 latency is <= 2s and P99 latency is <= 5s for payloads up to 10,000 seat changes
Guardrails And Edge-Case Warnings
When a simulation violates a policy (e.g., below minimum seats, plan cadence mismatch, discount expiring mid-period) Then the preview returns warnings[] entries with {type, code, message, severity} and blocks safe-apply for severity "blocking" When the effective window crosses a DST change or timezone boundary Then the preview displays the exact calculation window (start_ts, end_ts) in the subscription billing timezone and computes proration by actual seconds When multiple discounts or credits overlap Then the preview shows their stacking order and the resulting amounts per line-item And all guardrails are visible in UI and included in API/CSV outputs
Audit Trail & Recalculation
"As a compliance lead, I want a complete audit trail for proration so that we can satisfy audits and reproduce any historical invoice exactly."
Description

Captures an immutable audit trail for each proration event: inputs, formulas, versioned price books, timestamps, and resulting ledger entries. Supports deterministic recalculation and delta generation if policies or price books change, with idempotency keys to prevent duplicates. Provides export to CSV/JSON for compliance and a retention policy configurable per workspace.

Acceptance Criteria
Immutable Audit Record on Proration Event
Given a proration event is processed for a subscription within a workspace When the system computes the prorated charge Then it writes an immutable audit record capturing inputs (subscription_id, workspace_id, event_type, start_at, end_at, quantity, currency), formulas used, policy_version, price_book_version, timestamps (created_at UTC), and resulting ledger_entry_ids and monetary amounts (subtotal, taxes, total) And attempts to update or delete the audit record via any API or internal job return 409 Conflict and leave the original record unchanged And reading the audit record returns exactly the stored fields and values without mutation
Deterministic Recalculation on Price/Policy Change
Given an existing proration audit record references policy_version=p1 and price_book_version=v1 And a new policy_version or price_book_version is available When a recalculation is requested for the original event window with the new version(s) Then the recomputed amounts and line items are deterministically produced from the stored inputs and specified versions And running the same recalculation multiple times with identical inputs yields identical outputs And the recalculation record links to the original event_id and records recalculation_reason (e.g., price_book_change, policy_change)
Delta Generation and Balanced Ledger Adjustments
Given a recalculation changes the original total amount When delta generation is executed Then the system produces ledger adjustments consisting of a reversing entry for the original amount and a new entry for the recalculated amount, both linked to the original event and recalculation And the net effect across the generated entries equals (new_total - original_total) in the correct currency and sign And if (new_total - original_total) equals zero, no new ledger entries are created and the audit trail records reason="no_change" And all generated entries are balanced and pass accounting validation
Idempotency for Proration and Recalculation Requests
Given a client submits a proration or recalculation request with idempotency_key K within a workspace and operation_type When the exact same payload with key K is submitted again Then the system returns the original result (resource identifiers and values) and does not create additional audit records or ledger entries And when a different payload is submitted with the same key K, the system returns 409 Idempotency Key Conflict And idempotency is enforced across concurrent requests so that only one set of records is created
Audit Trail Export to CSV and JSON
Given a finance user requests an audit export for a workspace with a time range, format (CSV or JSON), and optional filters (subscription_id, event_type) When the export job completes Then the file contains all matching audit records with fields: event_id, workspace_id, subscription_id, event_type, start_at, end_at, quantity, currency, formulas, policy_version, price_book_version, subtotal, taxes, total, ledger_entry_ids, created_at And CSV output is UTF-8 with a header row, comma delimiter, and RFC-compliant quoting; JSON output is an array of objects with the same fields And the export artifact is retrievable via a signed URL and matches the requested filters and record counts
Configurable Retention Policy and Compliance Hold
Given a workspace sets an audit_retention_period in days and may apply a legal_hold at workspace or subscription level When the automated purge job runs Then audit records older than the retention period without an active legal hold are permanently deleted along with any associated export artifacts And records under legal hold are not deleted regardless of age until the hold is removed And attempts to retrieve a purged record return 404 Not Found and are logged with purge_reason="retention_expiry" And changes to the retention configuration are themselves recorded in a configuration audit log with timestamp and actor
Billing Integrations & Webhooks
"As an integrations engineer, I want proration events synced to our billing and ERP systems so that financial records stay consistent without manual reconciliation."
Description

Delivers idempotent webhooks and connectors for Stripe, Chargebee, and NetSuite to sync prorations, credit notes, and refunds. Implements retry with exponential backoff, signature verification, and dead-letter queues. Exposes read APIs to fetch proration details per invoice or series and maps external object IDs for end-to-end traceability.

Acceptance Criteria
Stripe Webhook Idempotency for Proration Events
Given a Stripe event with type in {invoice.created, invoice.updated, customer.subscription.updated} containing proration data and a unique event.id When the webhook is received the first time Then the system processes it once, upserts a single proration record, and returns HTTP 200 within 5 seconds p95 Given the same Stripe event.id is delivered again within 7 days When the webhook is received Then no duplicate side effects occur, the existing proration record remains unchanged, and HTTP 200 is returned Given multiple Stripe events affect the same invoice in quick succession When processing events Then idempotency keys/event IDs prevent duplicate external calls and the final stored proration state matches Stripe’s latest values
Chargebee Credit Note Sync and External ID Mapping
Given a proration results in a credit amount When syncing to Chargebee Then a Credit Note is created or updated with external_id = TimeTether proration_id and amount/currency match the proration within 0.01 Given the same proration is re-synced When the upsert is performed Then Chargebee acknowledges idempotent success without creating a duplicate and the credit note number remains unchanged Then Chargebee Credit Note metadata includes stripe_invoice_id and netsuite_transaction_id when available, and the mapping table stores {proration_id, chargebee_credit_note_id} for traceability
NetSuite Refund/Journal Sync with Traceability
Given a retro-credit or refund is due for an early cancellation When syncing to NetSuite Then a Credit Memo or Journal Entry is posted with externalid = TimeTether proration_id, amount equals proration total, currency matches invoice currency, and posting period equals invoice period Given the same sync is retried When NetSuite indicates a record with the same externalid already exists Then the operation is treated as success without duplicate posting and no additional financial impact occurs Then the system persists netsuite internalId and exposes it via the read API alongside the proration for end-to-end traceability
Exponential Backoff Retry Policy
Given a provider API call or webhook processing fails with a transient error (HTTP 5xx, network timeout, or HTTP 429) When retrying Then the system retries up to 5 times with backoff delays approximately 1m, 5m, 15m, 1h, 4h with ±20% jitter and honors Retry-After when present Given a non-retryable error (HTTP 4xx except 429) When encountered Then no further retries occur and the message is routed to the DLQ immediately with error details recorded Then all retries preserve idempotency keys/event IDs so no duplicate external objects are created
Provider Signature/Token Verification
Given a Stripe webhook with header Stripe-Signature When received Then the system validates the signature against the configured secret with maximum clock skew of 5 minutes and rejects invalid signatures with HTTP 400 and no side effects Given a Chargebee webhook with header chargebee-event-signature When received Then the system verifies the HMAC signature and rejects invalid requests with HTTP 400 and no side effects Given NetSuite connector calls require token/OAuth credentials When credentials are invalid or expired Then calls are rejected (or tokens refreshed securely where applicable) and no data changes occur until re-authenticated
Dead-Letter Queue and Alerting
Given a message exhausts maximum retries or is marked non-retryable When DLQ routing occurs Then the original payload, headers, error details, and correlation_id are preserved in the DLQ with a retention of at least 30 days Then an alert is sent to the on-call channel within 1 minute including tenant, provider, event.id, and error summary Given an engineer triggers a replay from the DLQ When replay is executed Then the message is reprocessed once using the original idempotency keys and, on success, it is removed from the DLQ
Read APIs for Proration Details
Given an authenticated caller When GET /billing/prorations?invoice_id={id} is called Then the API returns 200 with all proration records for that invoice including amounts, reasons, and external_ids for Stripe, Chargebee, and NetSuite, paginated (default 50, max 200) Given GET /billing/prorations?series_id={id} or GET /billing/prorations/{proration_id} When called with valid IDs Then the API responds in p95 < 300 ms for result sets ≤ 100 records and returns 404 for nonexistent resources Then every response includes correlation_id, created_at, updated_at, and mappings to external object IDs for end-to-end traceability

Cost Center Tags

Tag each series to projects, teams, or cost centers and optionally split allocations (percent split or attendance-weighted). Exports map directly to your GL or BI tools, making chargebacks painless and transparent across departments and programs.

Requirements

Series Tagging UI and API
"As a program manager, I want to tag a meeting series to specific projects, teams, or cost centers so that downstream reporting and chargebacks are accurate and consistent."
Description

Provide a streamlined UI and secure API to tag each recurring meeting series with one or more classifications (project, team, cost center). Include type-ahead search over a governed tag catalog, multi-select, validation against active codes, and clear visibility of applied tags on series and instances. Support bulk-tagging, default tags by team/workspace, and inline tagging during one‑click invite creation. Handle archived/inactive tags gracefully with warnings and suggested replacements. Ensure tags propagate to downstream allocation and export pipelines.

Acceptance Criteria
Typeahead Tag Search in Series Editor
Given a workspace with a governed tag catalog containing active and archived tags When a user types at least 2 characters in the Tags field of the series editor Then the UI displays up to 20 active tag suggestions within 500 ms (p90), ranked by exact match, prefix, then substring, case-insensitive And only tags scoped to the user’s workspace are suggested And the user can select multiple tags via mouse or keyboard (arrow keys + Enter) and each selection is added as a chip And duplicate tag selections are prevented with an inline notice
Validation Against Inactive or Archived Tags
Given a series has one or more inactive/archived tags selected via UI or API When the user attempts to save the series Then the save is blocked and an inline warning lists the inactive tags with up to 3 suggested replacements for each And replacing or removing all inactive tags enables the Save action And API requests respond 422 with error code TAG_INACTIVE and suggested replacements in the response payload And no partial changes are persisted until all inactive tags are resolved
Bulk Tagging on Series List
Given the series list view with selectable rows and bulk actions When a user selects between 10 and 500 series and applies Add Tags [A,B] and Remove Tags [C] Then the operation completes within 60 seconds (p95) and updates tags atomically per series And a results summary shows counts for updated, skipped, and failed, with a downloadable CSV of per-series errors And tags are merged without duplicates; remove only deletes matching tags And refreshing the list shows all updated series with the new tags
Default Tags by Team/Workspace and One-click Invite
Given a workspace or team has default tags configured [T1,T2] When a user creates a new series or initiates a one-click invite Then [T1,T2] auto-populate in the Tags field prior to save and are fully editable And API Create Series without explicit tags applies [T1,T2]; providing explicit tags bypasses defaults And audit logs record that default tags were applied automatically
Tag Visibility on Series and Instances
Given a series with applied tags When viewing the series detail page Then all tags render as read-only chips showing tag name and type (project/team/cost center) on hover And when viewing any instance of that series, the same tags display read-only And updates to series tags reflect on instance pages within 1 minute And instance attendance and schedule views include a Tags column with the series tags
Tagging API Security and Behavior
Given an API client authenticated with a workspace-scoped token When calling GET /series/{id}/tags or PATCH/PUT /series/{id}/tags Then only series within the token’s workspace are accessible; cross-workspace access returns 403 And the payload validates tag codes against active tags; inactive returns 422 TAG_INACTIVE with suggestions And updates are idempotent; repeating the same request does not create duplicate tags and returns 200 And all write operations are audit-logged with actor, timestamp, and before/after values
Downstream Propagation to Allocation and Exports
Given tags on a series are created, updated, or removed When allocation calculations and export jobs next run Then updated tags propagate to allocation logic used for chargebacks And the next scheduled GL/BI export includes the updated tags for the series and its instances And a manual export triggered within 5 minutes of the change includes the updated tags And background job failures surface to monitoring and retry up to 3 times with exponential backoff
Allocation Methods (Percent Split & Attendance‑Weighted)
"As a finance analyst, I want to choose between percent split and attendance‑weighted allocation for a meeting series so that costs are distributed fairly and transparently."
Description

Enable two allocation modes per series: (1) fixed percent split across selected tags, and (2) attendance‑weighted allocation that dynamically splits based on actual attendee attribution per tag. Enforce normalization to 100%, provide rounding rules, minimum thresholds, and preview of expected allocations before saving. Allow effective-date changes and per-instance overrides when necessary. Persist chosen method for exports and ensure recalculation when attendance or tags change.

Acceptance Criteria
Percent Split Entry & Normalization
Given a series with at least one selected cost center tag And the user selects the "Percent Split" allocation method When the user enters percentage values for each selected tag and clicks Save Then the system validates all values are numeric, >= 0.00, and allow up to 2 decimal places And if the sum of entered values equals 0.00 or no tags are selected, the Save is blocked with an inline error And on Save, the system normalizes the entered values so the stored and displayed percentages sum exactly to 100.00% And any rounding residue required to reach exactly 100.00% is assigned deterministically to tags in descending entered percentage order (stable by tag name for ties) And the saved allocation for the series reflects the normalized percentages
Attendance‑Weighted Calculation & Rounding
Given an occurrence with attendance records where attendees are mapped to selected cost center tags And the series allocation method is "Attendance‑Weighted" When allocations are calculated for the occurrence Then each tag’s share equals (count of attendees mapped to that tag) / (count of attendees mapped to any selected tag), rounded to 2 decimals using half‑up rounding And attendees without a mapped selected tag are excluded from both numerator and denominator and surfaced as "Unattributed" in the preview And the final stored/displayed percentages sum exactly to 100.00%
Minimum Thresholds & Redistribution
Given a configured minimum allocation threshold T% for the workspace or series When validating allocations Then in Percent Split mode, any non‑zero tag value entered below T% causes a validation error until the value is set to 0.00% or at least T% And in Attendance‑Weighted mode, any computed tag share below T% is set to 0.00% and its mass is redistributed proportionally across tags at or above T% And resulting percentages sum to 100.00%
Allocation Preview Before Save
Given the user is configuring tags and an allocation method for a series When the configuration form is open Then a Preview panel displays the expected allocations before saving And in Percent Split mode, the preview shows normalized percentages that sum to 100.00% And in Attendance‑Weighted mode, the preview shows projected percentages for the next 4 scheduled occurrences using attendance from the last 4 completed comparable occurrences; if insufficient data, the preview labels "Insufficient attendance data" and uses equal weighting across selected tags for projection And the preview updates within 500 ms when the user changes tags, method, or thresholds
Effective Date Change & Backfill Rules
Given a series currently using a specific allocation method When the user schedules a method change with an effective date D and saves Then all occurrences with start date < D retain their existing allocations and method metadata And all occurrences with start date >= D use the new method and are recalculated accordingly And exports for each occurrence include the method that was effective on that occurrence’s date And an audit log entry captures who changed the method, the effective date, and the before/after values
Per‑Instance Override Behavior
Given a specific occurrence of a series When the user applies a per‑instance allocation override and saves Then the overridden percentages are stored for that occurrence, sum to 100.00% with the same rounding rules, and are used in exports And subsequent attendance changes for that occurrence do not alter the overridden allocations unless the user resets to "Follow Series Method" And the UI marks the occurrence as "Overridden" and offers a "Reset to Series Method" action
Recalculation Triggers & Export Persistence
Given a series with saved allocation settings When any of the following occur: attendance is updated, tags on the series or attendee‑to‑tag mappings change, the minimum threshold changes, or the method changes with immediate effect Then allocations for affected non‑overridden, non‑locked occurrences are recalculated within 5 minutes And recalculated allocations replace prior values for future exports and create a new allocation version with a timestamp and change reason And exports include for each occurrence: the allocation method, tag IDs, tag names, percentages (2 decimals), and totals that sum to 100.00%
Attendance Attribution Engine
"As a team lead, I want attendance-weighted splits to reflect who actually attended and their organizational tags so that chargebacks match real utilization."
Description

Attribute each attendee to a tag dimension (e.g., cost center/team) using directory and HRIS mappings, then compute attendance‑weighted contributions per instance based on presence (headcount or duration‑weighted for partial attendance). Exclude guests if configured, handle unmapped attendees with fallback rules, and de‑duplicate cross‑calendar invites. Recalculate allocations on attendance updates and surface diagnostics for missing mappings to maintain data quality.

Acceptance Criteria
HRIS/Directory Tag Mapping Attribution
Given a meeting instance with attendees having directory and/or HRIS records And a configured mapping precedence and tag dimension When the engine runs attribution for the instance Then each eligible, non-guest attendee is assigned exactly one tag_id per the configured mappings And attendees with no valid mapping are marked Unmapped for diagnostics And the attribution payload includes attendee_id, tag_id (or Unmapped), mapping_source, and mapping_version
Duration-Weighted Attendance Contribution
Given attendance telemetry with join/leave timestamps for each eligible attendee And the instance weighting mode is set to duration-weighted When the engine computes contributions Then each eligible attendee’s contribution equals attendee_present_seconds divided by the sum of present seconds for all eligible attendees, rounded to 4 decimals And the sum of all eligible attendees’ contributions equals 1.0000 ± 0.0001
Headcount-Weighted Attendance Contribution
Given eligible attendees present for the instance are identified And the instance weighting mode is set to headcount-weighted When the engine computes contributions Then each eligible attendee’s contribution equals 1 divided by the count of eligible present attendees, rounded to 4 decimals And the sum of all eligible attendees’ contributions equals 1.0000 ± 0.0001
Guest Exclusion Configuration
Given an instance includes attendees marked guest=true And exclude_guests is enabled for the series or workspace When the engine computes attributions Then guest attendees are excluded from mapping and contribution calculations And totals are normalized across non-guest attendees to equal 1.0000 ± 0.0001 And when exclude_guests is disabled, those attendees are included and totals recomputed accordingly
Unmapped Attendee Fallback and No-Orphaned Allocation
Given one or more attendees lack a tag mapping in both directory and HRIS And a fallback rule is configured When the engine runs attribution Then each unmapped attendee is assigned per the configured fallback rule (e.g., Unassigned tag_id) And 0% of the instance’s contribution remains unassigned And a diagnostic entry is created for each fallback including attendee_id and fallback_rule_id
Cross-Calendar Invite De-duplication
Given a person appears as multiple invitees across calendars or aliases that resolve to the same user_id When the engine computes presence and contributions Then that person is counted once based on resolved user_id And their presence duration is computed as the union of overlapping intervals And duplicate invites do not inflate headcount or contributions
Recalculation on Attendance Updates
Given attendance data for an instance is updated (e.g., join/leave times corrected or RSVP status changes) When the engine processes the update or a recompute is triggered Then contributions and tag attributions for that instance are recalculated using the latest data And subsequent API reads and exports for that instance reflect the recalculated values And recomputation_version is incremented to support auditability
GL/BI Mapping & Export
"As a finance operations manager, I want exports that directly map to our GL/BI structures so that we can post chargebacks without manual reformatting."
Description

Provide configurable mappings from tags to GL accounts, cost center codes, and project IDs. Offer scheduled and on‑demand exports in CSV and JSON with delivery options (S3, secure email, API webhook). Include fields such as series ID, instance date/time, duration, chosen allocation method, normalized allocation percentages, attributed headcount/duration, and mapped finance codes. Support idempotent re‑exports, incremental windows, backfills, and schema templates tailored for popular GL/BI tools to minimize downstream transformation.

Acceptance Criteria
Scheduled CSV Export to S3 with Finance Code Mapping
Given a series has cost center tags mapped to GL account, cost center code, and project ID and a daily CSV export to S3 is scheduled When the scheduler runs at the configured time Then a CSV file is created in the configured S3 bucket/prefix with filename pattern export_{schedule_name}_{YYYYMMDD_HHMMSSZ}.csv And the CSV is UTF-8, RFC-4180 compliant with a header row And each row represents a single meeting instance allocation (one row per allocation target per instance) And each row includes fields in this order: series_id, instance_datetime_utc (ISO 8601, Z), duration_minutes (integer), allocation_method ("percent_split" or "attendance_weighted"), normalized_allocation_pct (0.00–100.00, 2 decimals), attributed_headcount (integer >= 0), attributed_duration_minutes (integer), gl_account, cost_center_code, project_id And mapped finance codes match the latest mapping configuration at export time And the export job status is recorded as success with record_count equal to the number of data rows written
On-Demand JSON Export via Webhook or Secure Email
Given an admin requests an on-demand export in JSON for a specific UTC date range When delivery method is Webhook with a valid HTTPS URL Then the system POSTs a JSON array of records matching the required schema and receives an HTTP 2xx from the endpoint And if a non-2xx response occurs, the job is marked failed and the response code and body are logged And the JSON schema includes: series_id, instance_datetime_utc (ISO 8601), duration_minutes, allocation_method, normalized_allocation_pct, attributed_headcount, attributed_duration_minutes, gl_account, cost_center_code, project_id When delivery method is Secure Email with a valid recipient address Then the recipient receives an email containing a secure download link to the JSON export file And the link requires recipient-specific access and expires within a configured TTL And the export job status reflects delivered when the email provider confirms acceptance
Idempotent Re-Export for a Fixed Window
Given an export is executed for an identical parameter set (date range, format, delivery target, schema template) When the export is re-executed without any underlying data or mapping changes Then the produced payload is byte-identical to the prior export And the record order and record count are identical And the job is marked as re_export=true in export metadata without generating duplicate downstream records when consumed
Incremental Export Since Last Successful Run
Given a schedule is configured for incremental exports And a last_successful_export_window_end watermark exists When the schedule runs Then only records with instance_datetime_utc > last_successful_export_window_end and <= now() are included And no record included in any prior successful run for this schedule is re-exported And after success, the watermark is advanced to the maximum instance_datetime_utc included
Backfill of Historical Meetings with Updated Allocations
Given a backfill export is requested for a historical date range And attendance or tag mappings have changed since original exports When the backfill runs Then the export reflects the current state at export time (updated mappings and attendance) And only records with instance_datetime_utc within the requested range are included And the export metadata marks the run as type=backfill
Allocation Normalization: Percent Split and Attendance-Weighted
Given a series uses percent_split allocation with N tagged targets When the export runs Then normalized_allocation_pct values are rounded to 2 decimals and sum to exactly 100.00 per instance (last target adjusted to fix rounding) And attributed_duration_minutes equals round(duration_minutes * normalized_allocation_pct / 100) per allocation Given a series uses attendance_weighted allocation When the export runs Then normalized_allocation_pct per target equals (attendees_in_target / total_attendees) * 100, rounded to 2 decimals and normalized to 100.00 And attributed_headcount equals attendees_in_target per instance
Schema Template Selection for Popular GL/BI Tools
Given a user selects a schema template (e.g., Generic CSV, NetSuite CSV, SAP CSV, Generic JSON) When an export runs with that template Then the column names, data types, ordering, date/time formats, and null representations match the template specification And only fields defined by the template are present, with exact header casing for CSV or exact keys for JSON And a schema_template identifier and version are recorded in the export metadata
Governance & Permissions
"As a finance administrator, I want controlled permissions and approvals around tagging and allocations so that financial data remains governed and compliant."
Description

Implement role‑based access controls to view, create, and edit tags and allocation settings. Support optional approval workflows for high‑impact changes, enforce required tagging for designated meeting types or folders, and restrict use of inactive/unauthorized tags. Notify stakeholders on changes and prevent edits in locked financial periods. Provide granular scopes (workspace, team, org unit) to align with enterprise governance.

Acceptance Criteria
RBAC for Tag and Allocation Management
Given a user with Viewer role When viewing a series' Cost Center Tags Then they can see current tags and allocation splits but cannot access edit controls. Given a user with Editor role scoped to the series' team When editing Then they can create, update, and remove tags and adjust allocation splits for that series. Given a user with Editor role whose scope excludes the series' team When attempting to modify Then the UI disables save and the API returns 403 AUTH-403. Given a user with Admin role at workspace scope When managing tag definitions Then they can create, edit, deactivate, and set authorization lists for tags within the workspace. Then all permission checks are enforced consistently across UI, API, and bulk import endpoints. And every denied action is logged with userId, role, scopeId, resourceId, action, timestamp, and outcome=denied.
Approval Workflow for High-Impact Changes
Given approvals are enabled and change sensitivity rules are configured (e.g., allocation delta ≥5% or cost center change) When a user submits such a change Then a change request is created with status=pending and effectiveDate captured. Then designated approvers for the impacted scope are notified immediately with before/after diffs. When an approver approves Then the change is applied at the start of the effectiveDate and an audit entry is recorded with approverId. When an approver rejects Then no changes are applied and the requester is notified with reason. While pending approval Then exports and invitations continue using the last approved configuration. If no approver acts within 3 business days Then reminders are sent daily until resolved.
Required Tagging for Designated Meeting Types and Folders
Given a folder or meeting type is configured as Tag Required When a series is created or edited within it Then save is blocked until all required tag categories are populated. Then the UI displays inline validation messages listing missing tags and the API returns 422 VAL-REQUIRED-TAG with the missingTagKeys. When a series is moved into a Tag Required folder Then the user is prompted to add missing tags before completion. When cloning a series from a Tag Required context Then tags are copied and validation re-run; cloning fails if required tags are missing or unauthorized.
Restrict Use of Inactive or Unauthorized Tags
Given a tag is inactive or the user lacks authorization for it When searching/selecting tags Then it does not appear in selectors and direct entry is rejected. When attempting to save a series with an inactive/unauthorized tag via API Then the request fails with 422 TAG-INACTIVE or 403 TAG-UNAUTHORIZED accordingly. When a series currently has a tag that later becomes inactive Then the UI shows a warning and prevents further edits until the tag is replaced; historical exports remain unaffected. Exports and chargeback files exclude inactive/unauthorized tags and include an error report listing impacted seriesIds.
Stakeholder Notifications and Audit Trail for Tag/Allocation Changes
When tags or allocation splits are created, updated, deactivated, or deleted Then notify series owners, tag owners, designated finance contacts, and approvers via email and in-app within 5 minutes. Then notifications include actor, timestamp, affected seriesIds, before/after values, effectiveDate, and links to review/approve. Then an immutable audit record is written with correlationId to tie UI actions to API calls and notifications. Users can subscribe/unsubscribe per tag or scope except mandatory finance recipients, who cannot opt out.
Prevent Edits in Locked Financial Periods
Given a financial period is locked for a workspace or cost center When a user attempts to create or modify tags or allocations effective within the locked period Then the UI disables save and the API returns 409 FIN-LOCKED with periodId. When an approval is pending for a change effective in a now-locked period Then it cannot be approved; approvers see a message and the request must be re-submitted for the next open period. When scheduling a change during a locked period Then the system auto-adjusts the effectiveDate to the next open period upon user confirmation. Exports generated for locked periods use the last approved configuration and include no pending or unapproved changes.
Granular Permission Scopes and Visibility
Given roles can be assigned at workspace, team, or org unit scope When evaluating permissions for a series Then access is granted only if the series' scope intersects the user's role scope. Tags can be visibility-scoped; users only see and apply tags within their assigned scopes in search, suggestions, and API responses. Cross-team series require that the user holds privileges covering all involved teams; otherwise, edits are blocked with 403 AUTH-SCOPE-MISMATCH. All audit entries and change requests capture scopeType and scopeId used in the decision.
Audit Trail & Effective Dating
"As an auditor, I want a detailed change history with effective dates so that I can reconcile allocations and ensure compliance during reviews."
Description

Capture a complete audit log for tag assignments, allocation method changes, mapping edits, and exports, including actor, timestamp, old/new values, and rationale. Support effective dating of changes with automatic recomputation for eligible periods, while protecting closed periods from modification. Expose audit views in-app and exportable logs to support reconciliation and compliance requirements.

Acceptance Criteria
Tag Assignment Change Logged
Given an existing series with one or more cost center tags And the user has permission to edit tags When the user updates tag assignments via UI or API Then the system writes a single immutable audit record containing: auditId, seriesId, orgId, actorId, actorEmail, actorRole, actionType="tag_assignment_changed", timestampUTC (ISO 8601), source (UI|API), oldValues, newValues, rationale And attempts to update or delete the audit record are rejected with 403 and no changes persist And the audit record appears in the series audit view within 5 seconds of save
Effective-Dated Allocation Recalculation
Given a series currently using percent split allocations And an open finance period exists covering the effective date and onward When a user changes allocation method to attendance-weighted with an effectiveDate and saves Then the system recalculates allocations for all eligible meetings on or after the effectiveDate in open periods only And no recalculation occurs for dates in closed periods or before the effectiveDate And an audit entry records methodFrom, methodTo, effectiveDate, affectedRange, meetingCountRecomputed, initiatedBy, jobId And recalculation completes within 15 minutes for up to 10,000 meetings, or progress and completion status are visible in a job tracker
Closed Periods Protected from Modification
Given one or more finance periods are marked Closed When a user attempts to save a change whose effective date intersects any closed period Then the system prevents changes from applying to closed periods and applies them starting from the first Open period on or after the effective date And the user sees a message listing protected periods and the adjusted effective start date And an audit entry is created with outcome="blocked_for_closed_period", attemptedEffectiveDate, protectedPeriods, and actor
Audit Log Export to GL/BI
Given an admin opens Audit Log Export When the admin exports with filters (dateRange, seriesId, costCenterId, actorId, actionTypes) in CSV or JSON Then the file includes: auditId, entityType, entityId, actionType (tag_assigned|allocation_method_changed|mapping_edited|export_run|audit_log_exported), actorId, actorEmail, timestampUTC, source, effectiveDate, oldValues, newValues, rationale, mappingVersion, periodStatus And the row count equals the filtered result count And the file is UTF-8 encoded with a stable column order and includes metadata: generatedAtUTC, filterSummary, rowCount, fileHashSHA256 And the export action is itself logged with actionType="audit_log_exported", exportId, filterSummary, and fileHashSHA256
In-App Audit View with Filtering
Given a user opens a series Audit tab When the user filters by actionType, actor, and dateRange and sorts by timestamp Then the list updates within 2 seconds for up to 1,000 records and paginates at 50 rows per page And selecting an entry shows a field-level diff of oldValues vs newValues And timestamps display in the user’s local timezone with a tooltip for UTC And an Export button is enabled only when at least one record matches the filters
Rationale Capture Enforcement
Given an action type is tag assignment change, allocation method change, mapping edit, or export When the user submits without a rationale of at least 5 non-whitespace characters Then validation blocks submission and shows an inline error requiring a rationale And upon valid submission, the rationale is persisted and included in the audit record and in exports
Mapping Edits With Effective Date and Versioning
Given an existing GL/BI mapping for cost center tags with version v When a user edits the mapping with an effectiveDate D and saves Then the system creates version v+1 and logs an audit entry with mappingId, oldMapping, newMapping, effectiveDate, actor And the new mapping applies only to transactions on/after D in open periods; closed periods continue to reference version v And exports include the mappingVersion used per row; previously generated exports remain unchanged
Tag Catalog Sync & Validation
"As a systems integrator, I want the tag catalog to sync from our source of truth and validate at entry so that all tags are accurate and reconcilable with finance systems."
Description

Synchronize and validate tag catalogs (projects, teams, cost centers) from source systems such as HRIS/ERP/Directory via connectors or CSV import. Handle aliases, de‑duplication, active/inactive status, and hierarchical relationships. Provide health checks for missing or stale codes and offer remediation workflows. Enforce catalog validation at tag assignment time to prevent orphan or invalid codes from entering the pipeline.

Acceptance Criteria
HRIS/ERP Connector Sync: Alias Resolution and De‑duplication
Given a connected HRIS/ERP source with tags including duplicates, case/whitespace variants, and configured alias mappings When a scheduled catalog sync runs Then the system creates or updates exactly one canonical tag per unique external_id or alias group And all case/whitespace variants are normalized and mapped to the canonical record as aliases And duplicates are consolidated without loss of external_id linkage and source attribution And a sync report is produced with counts for attempted, created, updated, deduplicated, skipped, and failed And the sync completes within 2 minutes for catalogs up to 1,000 tags And no canonical tags exist with identical normalized names after sync
CSV Import: Pre‑flight Validation and Error Report
Given an admin uploads a CSV containing columns code, name, status, parent_code, aliases When a Dry Run validation is initiated Then the system validates required columns, unique codes (case-insensitive), valid statuses in {active,inactive}, non-conflicting aliases, and existing parent_code references And returns a downloadable error CSV with row number, field, error_code, and message for each violation And no records are persisted during Dry Run When an Import is executed after a zero-error Dry Run Then valid rows are upserted atomically; if any fatal errors occur, zero rows are persisted And the import of up to 10,000 rows completes within 5 minutes And an audit log entry is recorded with actor, file hash, counts (created, updated, skipped), and duration
Active/Inactive Status Sync and Enforcement
Given some tags are marked inactive in the source system or are missing from the latest source snapshot When the catalog sync completes Then those tags are marked inactive in the application catalog without deletion And any new tag assignment attempts using inactive tags are blocked with error code TAG_INACTIVE and a user-readable message And existing series that reference now-inactive tags remain intact but display an "Inactive Tag" banner with remediation guidance And deactivation enforcement takes effect within 5 minutes of sync completion And reactivating a tag in the source propagates on the next sync and removes the banner automatically
Hierarchy Validation and Cycle Prevention
Given tags may reference parent_code to form hierarchies (e.g., Cost Center > Team > Project) When tags are imported or synced Then every tag with a parent_code must reference an existing, active parent within the same catalog And any change that would introduce a cycle is rejected with error code HIERARCHY_CYCLE_DETECTED And hierarchy depth is limited to a maximum of 6 levels; deeper structures are rejected with error code HIERARCHY_DEPTH_EXCEEDED And reparenting updates the full ancestry path atomically so reads never observe partial hierarchies And a validation report lists orphan children, cycle attempts, and depth violations with counts
Real‑time Tag Assignment Validation in Series Editor
Given a user is creating or editing a meeting series and assigning cost center tags with split allocations When the user attempts to save the series Then each selected tag must exist in the catalog and be active at save time using the latest validated catalog version And percent-split mode requires splits to sum to exactly 100% with precision to one decimal place And attendance-weighted mode is available only if attendance tracking is enabled for the series And mixing split modes within a single series is rejected with error code SPLIT_MODE_CONFLICT And on validation failure, the Save action is blocked and field-level errors indicate the exact issue and offending tag(s) And on success, the series is saved and the applied catalog version id is stored with the series for audit and replay
Health Checks and Remediation Workflow
Given nightly health checks are configured for tag catalogs When the health job runs Then it computes and displays per-catalog status {OK,WARN,FAIL} based on thresholds for: missing required codes on active series (>0 = FAIL), stale catalogs (last successful sync > 24h = WARN, > 48h = FAIL), orphan assignments (>0 = FAIL), and alias collisions (>0 = FAIL) And sends notifications on FAIL to catalog owners via email and Slack with deep links to remediation And the remediation UI allows: mapping an orphan assignment to a valid tag, creating an alias to merge duplicates, or requesting source-of-truth changes And remediation actions are audit-logged with actor, timestamp, before/after, and affected series count And reprocessing updates affected series within 10 minutes for up to 500 series and clears corresponding health check findings

Series Line Items

Itemized invoices list each recurring series with owner, cadence, active days, rate, and adjustments. Download CSV/JSON or push to ERP. Auditors and budget owners can trace every dollar to a specific series, reducing reconciliation time and back-and-forth.

Requirements

Series Attribution Data Model
"As a billing engineer, I want a normalized, versioned schema for series-level invoice lines so that calculations, exports, and audits are consistent, traceable, and scalable."
Description

Design and persist a normalized schema for itemized invoice lines that attributes every charge to a specific recurring series in TimeTether. The model must capture series_id, invoice_period_start/end, owner_id and owner_type (user/team), team_id, cadence (e.g., weekly/bi-weekly/monthly), active_days, time_zone, contracted_rate, currency, quantity (occurrences/hours), adjustment entries (proration, discounts, credits), tax_code, cost_center, and tags. Enforce referential integrity to scheduling data, include versioning with effective dates for rate changes, and record calculation_breakdown fields for transparency. Provide idempotent keys (series_id + period) to avoid duplicate lines, validation rules for required fields and currency/rate formats, and indexes for high-volume queries. Expose the model via internal services for export and ERP connectors, ensuring backward-compatible schema evolution.

Acceptance Criteria
Idempotent Persistence by Series and Period
- Given a valid invoice line payload containing series_id, invoice_period_start, and invoice_period_end, When an upsert is performed using the composite key (series_id, invoice_period_start, invoice_period_end), Then exactly one row exists per composite key and subsequent identical upserts do not create duplicates. - Given any required field among [series_id, invoice_period_start, invoice_period_end, owner_id, owner_type, cadence, time_zone, contracted_rate, currency, quantity] is missing or null, When an insert is attempted, Then the operation fails with HTTP 422 and no row is persisted. - Given a successful insert, When the row is retrieved, Then all provided fields including active_days, tax_code, cost_center, tags, and adjustment entries are persisted with exact values.
Referential Integrity to Series and Ownership
- Given a series_id that does not exist in scheduling.series, When an invoice line is inserted, Then the insert is rejected by a foreign key constraint. - Given owner_type not in {'user','team'}, When an invoice line is inserted, Then the insert is rejected by a check constraint. - Given owner_type='user' and owner_id not found in users, When inserting, Then the operation is rejected; If team_id is provided, it must exist in teams. - Given owner_type='team', When inserting, Then owner_id must exist in teams and team_id, if provided, must equal owner_id; otherwise the insert is rejected. - Given an attempt to delete a referenced user, team, or series, When outstanding invoice lines exist, Then deletion is restricted and fails.
Versioned Rates and Effective Dating
- Given multiple rate versions for a series with non-overlapping effective_start/effective_end, When generating an invoice line for a period that falls within a single version, Then contracted_rate and currency correspond to that version. - Given a period overlapping two or more rate versions, When the line is generated, Then calculation_breakdown includes one segment per applicable version with correct date range and amounts prorated by occurrences/hours in that range, and the sum of segment.amounts equals contracted_rate*quantity adjusted by any adjustments within ±0.01 currency units. - Given a new rate version whose effective window overlaps an existing version, When saving, Then validation fails and the version is not stored. - Given historical invoice lines already issued, When a new future-dated rate version is added, Then existing lines remain unchanged.
Field Validations and Domain Formats
- Given currency is not a valid ISO 4217 uppercase code, When inserting/updating, Then validation fails. - Given contracted_rate < 0 or precision exceeds 6 decimal places, When inserting/updating, Then validation fails. - Given quantity <= 0 or is not a numeric value, When inserting/updating, Then validation fails. - Given cadence not in {'weekly','bi-weekly','monthly'}, When inserting/updating, Then validation fails. - Given active_days contains values outside {'Mon','Tue','Wed','Thu','Fri','Sat','Sun'}, When inserting/updating, Then validation fails. - Given time_zone not in the IANA tz database, When inserting/updating, Then validation fails. - Given tax_code or cost_center not found in configured registries (if provided), When inserting/updating, Then validation fails.
Transparent Calculation Breakdown and Adjustments
- Given an invoice line with proration, discounts, and credits, When saved, Then calculation_breakdown stores machine-readable entries with fields [type, start, end, unit_rate, quantity, amount, currency, reason] and adjustments store [type, amount, currency, reason]. - Given the persisted calculation_breakdown, When recomputing totals, Then sum(unit_rate*quantity across segments) plus sum(adjustments.amount) equals exported line amount within ±0.01 of the currency. - Given retrieval via internal services, When the line is fetched, Then calculation_breakdown is returned unchanged and is valid against the defined JSON schema.
Indexed Queries and Export/ERP Exposure
- Given indexes on (series_id, invoice_period_start), (invoice_period_start, invoice_period_end), (owner_id, owner_type), and (cost_center), When querying by series over a 12-month period, Then p95 latency is <= 200ms for returning up to 10,000 rows on a dataset of at least 5 million lines. - Given a request to export lines for a period and cost_center, When invoking the export service, Then CSV and JSON outputs include all required fields with stable column/field names and complete within 60 seconds for up to 10,000 lines. - Given an ERP connector push with idempotency key (series_id, invoice_period_start, invoice_period_end), When retried, Then no duplicate documents are created in the ERP and responses are 2xx idempotent.
Backward-Compatible Schema Evolution
- Given introduction of a new optional field, When the migration runs, Then existing reads/writes continue to succeed without application changes and the field has a default or is nullable. - Given deprecation of a field, When rolling out, Then the API continues to accept the field for at least one version and emits deprecation warnings, and contract tests for prior API versions pass. - Given a migration failure at any step, When rolling back, Then the database returns to the previous schema and data without loss. - Given connectors using the previous schema, When the new schema is deployed, Then connector integrations continue to pass end-to-end tests without code changes.
Automated Line Item Generation Engine
"As a finance operations manager, I want line items automatically generated per series each billing period so that invoices reflect accurate usage with proration and adjustments without manual effort."
Description

Implement a scheduled job and service that generates one invoice line per active recurring series for each billing period, pulling authoritative metadata (owner, cadence, active days, time zone) from TimeTether’s scheduling engine and rates from billing configuration. Calculate amounts using rate x quantity, applying proration for partial periods, skipped/canceled occurrences, and mid-period rate changes via effective-dated adjustments. Produce calculation_breakdown details (inputs, formulas, outcomes) on each line for auditability. Ensure idempotent reruns per period, deduplication, timezone-safe close times, and backfill capabilities for prior periods. Emit events (created/updated) for downstream export and ERP push, and surface metrics/alerts for generation failures.

Acceptance Criteria
Periodic Line Generation: One Line per Active Series
Given billing period P is closed for tenant T When the generation job runs Then exactly one invoice line is created per series that is active at least one day in P, each uniquely keyed by (tenant_id, series_id, period_id) Given a series is inactive for all days in P When the generation job runs Then no invoice line is created for that series for P Given generation completes When inspecting all lines for P Then the count of lines equals the count of distinct active series during P
Authoritative Metadata and Rate Sourcing
Given a line is generated for series S in period P When populating metadata Then owner, cadence, active_days, and time_zone are sourced from the scheduling engine snapshot effective in P and include a metadata_source="scheduling_engine" and metadata_version Given a line is generated for series S in period P When populating pricing Then rate and currency are sourced from billing configuration effective at the start of each pricing segment in P Given required metadata is missing or stale When the job attempts to generate a line Then the line is not created and the failure is recorded with error_code="METADATA_MISSING" and correlation_id
Calculation Breakdown Auditability
Given a line is generated When persisting Then calculation_breakdown contains inputs (rate, currency, quantity, active_days, period_start, period_end, time_zone, skipped_count, canceled_count, adjustments[], segments[]), formulas, and outcomes, compliant with schema_version="v1" Given calculation_breakdown is present When recomputing amount from breakdown Then recomputed_amount equals line.amount within 0.01 currency units Given multiple segments exist in P When inspecting calculation_breakdown.segments Then each segment lists date_range, rate, quantity, amount and the sum of segment amounts equals line.amount Given line.amount is computed When rounding Then monetary values are rounded using currency minor units with half_up rounding
Proration and Skips Handling
Given a series S has standard period quantity Q_std and is active for D_active of D_total billable days in P When computing quantity Then quantity = Q_std * (D_active / D_total) rounded to 2 decimals Given scheduled occurrences in P = 4 and 1 is skipped and 1 is canceled When computing quantity Then quantity decreases by 2 and amount = rate x quantity Given a series has zero active days in P When the job runs Then no line is generated for S for P
Effective-Dated Adjustments
Given the rate for S changes during P with effective dates E1..En When computing amount Then the period is split into segments by effective dates and each segment applies its rate to its segment quantity; line.amount equals the sum across segments Given an adjustment exists with reason_code and effective date in P When generating the line Then calculation_breakdown.adjustments includes the adjustment with amount, reason_code, effective_date, and sign, and line.amount reflects the net of adjustments Given a retroactive rate change is introduced after initial generation When the job reruns for the same period Then the existing line is updated in place with a new version and recalculated amount
Idempotency and Deduplication
Given the job has already successfully generated lines for period P When the job is rerun with identical inputs Then no additional lines are created, existing line IDs remain unchanged, and a deduplication metric rerun_noop is incremented Given multiple workers process the same period concurrently When the job runs Then at most one line exists per (tenant_id, series_id, period_id), enforced by a uniqueness constraint, and retries back off on conflicts Given a partial failure occurs mid-run When the job restarts Then only missing or failed lines are (re)generated and completed lines are not duplicated
Timezone-Safe Close, Backfill, and Events/Telemetry
Given tenant T has billing timezone TZ When closing period P Then P boundaries are computed using TZ at 00:00:00 TZ start to 23:59:59 TZ end and lines use TZ for all date math Given a backfill is requested for periods P1..Pk When the backfill job runs Then lines are generated for each requested prior period using authoritative metadata and rates effective in those periods and marked with backfill=true in calculation_breakdown Given a line is created or updated When the job completes processing Then an event invoice_line.created or invoice_line.updated is emitted within 60 seconds containing line_id, tenant_id, series_id, period_id, amount, currency, version, and checksum Given any generation error occurs When detected Then a metric generation_failure_count is incremented and an alert is issued with error_code, tenant_id, period_id, and correlation_id, and the job schedules a retry with exponential backoff
Downloadable CSV/JSON Export
"As an auditor, I want to download CSV/JSON exports of itemized series lines with filters so that I can trace every charge offline and reconcile quickly."
Description

Provide UI and API endpoints to export itemized series line items as CSV and JSON. Support filters (date range, series_id, owner/team, cost center, status), selectable columns, and inclusion of calculation_breakdown. Implement streaming/paginated exports for large datasets, stable column ordering, UTF-8 encoding, and consistent numeric/currency formatting. Generate signed URLs for downloads, apply role-based scoping, and log export events for compliance. Include a versioned data dictionary and schema in metadata headers for consumers. Ensure exports are deterministic and reproducible for the same inputs.

Acceptance Criteria
UI CSV Export with Filters and Selectable Columns
Given an authorized user with access to at least one series And a dataset containing multiple series across different owners, teams, cost centers, and statuses within a specified date range When the user opens the Series Line Items export UI And applies filters: date_range=2025-05-01..2025-05-31, owner=alice, cost_center=CC-100, status=active And selects columns: [series_id, owner, team, cadence, active_days, rate, adjustments, total_amount] And chooses format=CSV and clicks Generate Then the downloaded file contains only rows that match all filters and the user's role scope And the CSV includes exactly the selected columns in the selected order with stable column ordering across identical requests And the CSV is encoded in UTF-8 without BOM And numeric values use dot as decimal separator with fixed 2 decimal places for currency amounts, no thousands separators, and ISO-4217 currency codes where applicable And column headers match the versioned data dictionary field names
API JSON Export with Pagination and Streaming
Given an API client with a valid token scoped to export:read And more than 1,000,000 matching line items When the client requests GET /api/v1/exports/series-line-items?format=json&page_size=10000 Then the API responds with HTTP 202 and a job resource for async processing or streams results using chunked transfer encoding And when polling the job resource, the client receives a pre-signed download URL upon completion And the JSON response is UTF-8 encoded and delivered as NDJSON when stream=true or as a JSON array when stream=false And each page or chunk uses a deterministic sort: primary=series_id, secondary=occurrence_date, tertiary=line_item_id And no page overlaps or gaps occur across pagination; total_count and page_count are accurate
Secure Download: Signed URLs and Role-Based Scope Enforcement
Given a completed export requested by a user within org=O1 and role=Auditor When the system generates a download link Then the link is a signed URL bound to org=O1 and the requesting user's role scope and format And the signed URL expires within 60 minutes by default (configurable) and returns HTTP 403 after expiry And attempts to access out-of-scope series_id via the signed URL return HTTP 403 (API) or a UI error And key rotation does not invalidate already-issued URLs within their TTL
Deterministic and Reproducible Outputs
Given identical inputs: filters, selected columns, format, explicit sort definition, include_calculation_breakdown flag, and schema_version And no changes to underlying data between requests When two exports are generated at different times Then the file contents are byte-for-byte identical, including row order, headers, numeric formatting, and metadata headers And a SHA-256 checksum of the payload is included in metadata to verify integrity
Metadata Headers with Versioned Data Dictionary and Schema
Given any export (CSV or JSON) When it is delivered to the client Then HTTP response headers include X-Export-Version, X-Schema-Version, X-Data-Dictionary-URL, and X-Checksum-SHA256 And for CSV, the first lines include commented metadata entries (# key: value) reflecting the same values And for JSON, a top-level _meta object includes export_version, schema_version, data_dictionary_url, and checksum And all fields present in the file are defined in the referenced schema version
Calculation Breakdown Inclusion
Given a user selects include_calculation_breakdown=true When exporting CSV Then the file contains additional columns prefixed calculation_breakdown. (e.g., calculation_breakdown.base_rate, calculation_breakdown.adjustments, calculation_breakdown.proration, calculation_breakdown.currency_conversion) with consistent numeric formatting And when exporting JSON Then each line item includes a calculation_breakdown object with the same fields and values And when include_calculation_breakdown=false Then no calculation_breakdown fields are present in the output
Export Event Logging for Compliance
Given any export request, generation, download, or failure When the event occurs Then the system writes an immutable audit log entry containing timestamp, user_id, org_id, request_id, role, filters, selected_columns, format, include_calculation_breakdown, record_count, file_size_bytes (if known), export_version, schema_version, and outcome And audit entries are queryable by compliance users within 5 minutes of event time And attempts with insufficient permissions are logged with outcome=denied and reason
ERP Push Connectors
"As a controller, I want series line items pushed to our ERP with mapping, idempotency, and robust error handling so that accounting can close the books without manual re-entry."
Description

Build configurable connectors to push series line items into supported ERP systems (e.g., NetSuite, QuickBooks, SAP) as invoices or journal lines. Provide field mapping from the internal schema to ERP objects, including tax codes, cost centers, and custom fields. Support batching and chunking, idempotency keys to prevent duplicates, retries with exponential backoff, and a dead-letter queue for failures. Implement OAuth2/API key authentication, sandbox/test modes, and environment separation. Emit detailed success/failure logs, surface actionable error messages, and allow replay of failed batches. Provide a dry-run/preview mode to validate mappings before posting to production.

Acceptance Criteria
Field Mapping to ERP Invoice and Journal Objects
Given a configured connector for an ERP with mappings for tax_code, cost_center, currency, and custom fields And a set of series line items with owner, cadence, active days, rate, and adjustments When the user selects target "Invoice" and executes a post in the sandbox environment Then each eligible series produces one ERP invoice line with values mapped per the mapping table and currency preserved And tax_code values map to valid ERP tax codes; invalid values trigger a per-item validation error identifying the field and value And cost_center values map to ERP segments/departments; unmapped values cause the item to be skipped with an error while other items proceed And pre-provisioned custom fields are populated; missing custom fields produce a run-level warning listing missing fields without blocking other posts And the posting summary displays count_created, count_skipped, count_failed, and total_amount; totals equal the sum of rate plus/minus adjustments And ERP record IDs/document numbers/links are persisted per item for audit traceability
Dry-Run/Preview Validation Before Production Post
Given dry-run mode is enabled and environment is Production And field mappings are configured When the user runs the connector Then no records are created or modified in the ERP And a preview report is generated with counts per target object, the first 5 rendered payloads per object, and a complete list of validation issues by item And each validation issue includes a normalized error code, field path, sample value, and suggested remediation And the user can download the preview report as JSON and CSV And running the same job immediately after with dry-run disabled produces equal count_created totals (assuming no external data changes)
Idempotent Posting Prevents Duplicates
Given a batch of N series line items prepared with idempotency key K and stable payload hashes When the batch is posted Then exactly N ERP records are created and ERP IDs are stored with their payload hashes and key K When the exact same batch is posted again with key K Then zero new ERP records are created; each item returns status "skipped_duplicate" and the original ERP IDs remain unchanged When any item’s payload differs but the same key K is reused Then the request fails with a conflict (e.g., 409) and no new records are created for that request And posting the modified payload with a new key K2 creates new records (subject to ERP duplication rules)
Batching, Chunking, Retries, and Dead-Letter Queue
Given 5,000 line items and a configured batch size of 500 When the connector runs Then exactly 10 batches are sent, respecting ERP per-request limits (e.g., max lines per request) And each batch includes a correlation_id, idempotency key, and sequence number When an API rate-limit or transient network error occurs for a batch Then the connector retries up to 3 times with exponential backoff (e.g., ~1s, ~2s, ~4s with jitter) And if the batch still fails, it is moved to the dead-letter queue (DLQ) with payload snapshot, normalized error code, error message, retry_count, and correlation_id And remaining batches continue to process And the run summary reports batches_sent, batches_succeeded, batches_failed, and total_runtime
Replay of Failed Batches from Dead-Letter Queue
Given the DLQ contains failed batches with their original idempotency keys When a user initiates a replay for selected DLQ entries Then only those selected batches are reposted, preserving or regenerating idempotency keys based on the user’s choice And successful replays are removed from the DLQ and marked delivered with new timestamps And repeated failures remain in the DLQ with incremented retry_count and updated last_error And the replay run produces a summary with counts of reprocessed, succeeded, failed, and skipped
Authentication and Environment Separation
Given an OAuth2-based connector with valid client credentials When an access token is within 2 minutes of expiry during a run Then the connector refreshes the token before making the next ERP request and proceeds without failing the batch And token/secret values are never written in plaintext to logs or exports Given an API-key-based connector Then the API key is stored encrypted at rest and masked in UI and logs Given sandbox and production environments are configured with distinct credentials and endpoints When running in sandbox, records are posted only to sandbox; when switching to production, an explicit confirmation is required and a banner indicates environment And cross-environment leakage is prevented by configuration isolation and separate audit logs
Structured Logging and Actionable Error Reporting
Given a connector run is executed Then structured logs are emitted per batch and per item including timestamp, environment, ERP, endpoint, correlation_id, idempotency_key, durations, and result status And on errors, logs include ERP error code, HTTP status (if applicable), field path(s), offending value(s), and a human-readable remediation hint And a per-run summary log aggregates counts of created/skipped/failed items and batches, plus total runtime And logs are exportable in JSONL format and filterable by correlation_id and environment
End-to-End Audit Trail and Traceability
"As an external auditor, I want to drill from any invoice line to the originating series and change history so that I can verify completeness and accuracy of billed amounts."
Description

Create an immutable audit trail linking each invoice line item to its source series, scheduling decisions, rate configuration, and ERP transaction IDs. Record who/what/when for all mutations (rate changes, adjustments, recalculations), including before/after values and effective dates. Provide a drill-down UI from invoice line to series details and occurrence history, with exportable audit logs for evidence packs. Protect integrity with append-only logs and checksums, enforce retention policies, and make all audit artifacts queryable via API for auditors and budget owners.

Acceptance Criteria
Immutable Append-Only Audit Log with Checksums
Given the system is configured for audit logging When any auditable event occurs (create/update to rates, adjustments, recalculations, ERP export) Then an append-only log entry is created containing: event_id (UUIDv4), actor_type, actor_id, action_type, target_type, target_id, before_value (JSON), after_value (JSON), effective_from (RFC3339 UTC), effective_to (nullable RFC3339 UTC), occurred_at (RFC3339 UTC), invoice_line_id, series_id, request_id, prev_hash (hex), entry_hash (SHA-256 hex) And attempts to modify or delete an existing entry are blocked and return HTTP 403 with error code AUDIT_IMMUTABLE And the entry_hash validates as SHA-256(prev_hash + canonical_json(entry)) And a chain verification job validates a continuous hash chain per series and per invoice with zero breaks
Invoice Line Item to Series and Occurrence Drill-Down UI
Given an invoice with at least one line item linked to a series with occurrences in the billing period When the user clicks "View audit trail" on that line item in the UI Then a panel opens showing: series_id, series_name, owner, cadence, active_days, timezone_policy, rate_plan_name, base_rate, adjustment_rules, fairness_rotation_state (as of invoice time) And the panel lists all occurrences within the invoice period with: occurrence_id, scheduled_start/end (RFC3339 UTC), participant_timezones, decision_reason, rotation_position, status And each occurrence links to its decision record and related audit entries And 95th percentile time to first paint of the panel is <= 2.0 seconds for up to 500 occurrences
Complete Mutation History with Before/After and Effective Dates
Given a series that undergoes multiple rate changes and adjustments across a billing period When the audit log for that series is queried for the same period Then every mutation event is present with before_value, after_value, and effective_from/effective_to populated And invoice amount calculations reference the rate/adjustment effective at each occurrence time And recalculation events include reason, initiated_by, and affected_invoice_line_ids And events are strictly ordered by occurred_at then event_id to break ties
ERP Transaction Traceability from Invoice Lines
Given an invoice line item that was exported to the ERP When the ERP returns transaction identifiers for the line and any subsequent adjustments Then the invoice line shows erp_transaction_ids for original and adjustment entries with timestamps and statuses And the API GET /invoices/{id}/lines includes erp_transaction_ids[] and erp_push_status fields And failed ERP pushes emit audit events with error_code, error_message, and retry_count And subsequent successful retries link to the same invoice_line_id and prior failure event via correlation_id
Exportable Audit Logs for Evidence Packs (CSV/JSON)
Given a user with Auditor role requests an export for an invoice line’s audit trail When the user selects CSV or JSON and confirms export Then the generated file includes all audit fields and metadata (generated_at, requested_by, filters, record_count) And a SHA-256 checksum file (.sha256) is provided and matches the file contents And exports of up to 50,000 events complete within 60 seconds P95 and are delivered via secure, expiring URL (>= 15 min TTL) And the export includes the terminal chain hash to enable external chain verification
Audit API Queryability, Filtering, and Pagination
Given the public Audit API is available When a client calls GET /audit-logs with filters (series_id, invoice_line_id, actor_id, action_type, occurred_at_from/to, erp_transaction_id) and page_size=200 Then the response is 200 OK with items[], next_cursor, total_estimate, and each item contains required audit fields And invalid filter combinations or values return 400 with machine-readable validation errors And responses are stable-sorted by occurred_at then event_id, and pagination is consistent across pages for the same cursor And the endpoint publishes standard rate limit headers and returns 429 on sustained excess
Retention Policy Enforcement and Integrity Monitoring
Given the workspace retention policy is set to 7 years and WORM storage is enabled When any actor attempts to delete or alter audit entries within the retention window Then the operation is rejected with 403 and error code RETENTION_WORM_ENFORCED and an audit event is appended for the attempt And after the retention period elapses, entries are only purgeable via a controlled retention job that writes a tombstone with reason and purged_at And a daily integrity check verifies 100% of hash chains and emits a HIGH alert if any break is detected And the current retention policy and status are readable via GET /audit/policy returning policy_name, duration_years, storage_class, last_integrity_check_at, last_check_status
Role-Based Access and Redaction Controls
"As a budget owner, I want access limited to my teams’ itemized lines with sensitive fields redacted so that I can review spend without exposing unnecessary personal data."
Description

Implement fine-grained RBAC to control who can view, export, and push series line items. Define roles (Admin, Finance, Auditor, Budget Owner) and scopes (organization, business unit, team), enforcing least-privilege access. Redact PII and non-essential fields for auditor/budget-owner views while allowing finance admins full detail. Integrate with SSO groups, log access events, and require approval workflows for ERP pushes above configurable thresholds. Ensure permissions apply consistently across UI, API, exports, and connectors.

Acceptance Criteria
Scoped View Permissions by Role and Scope
Given a Finance user with scope "Business Unit A" When they request series line items for "Business Unit A" via UI or API Then the response status is 200 and the record set contains only items whose scope equals "Business Unit A" Given the same user requests series line items for any scope outside "Business Unit A" When the request is made via API Then the response status is 403 and zero records are returned Given an Auditor with scope "Team T" When they request series line items for "Team T" Then the response status is 200 and the record set contains only items whose scope equals "Team T" Given an Admin with scope "Organization" When they request series line items Then the response status is 200 and the record set contains all items in the organization
Export Authorization and Redaction for CSV/JSON
Given a Finance or Admin user with organization scope When they export series line items as CSV and JSON Then the files include all fields, including owner_full_name, owner_email, calendar_id, and internal_notes with original values Given an Auditor with organization scope When they export series line items as CSV and JSON Then the files contain only rows within their scope and the fields owner_full_name, owner_email, calendar_id, and internal_notes are replaced with "[REDACTED]" in all rows Given a Budget Owner with team scope "Team T" When they export series line items Then the files contain only "Team T" rows and PII/non-essential fields are redacted as "[REDACTED]" Given a user without export permission When they attempt an export via UI or API Then the response status is 403 and no file is generated and an access event is logged
ERP Push Permissions and Approval Workflow
Given a Finance user with push permission scoped to "Business Unit A" and an approval_threshold of 10000 for that scope When they push an ERP batch with total_amount equal to 8000 Then the push executes immediately, returns status "Pushed", and an audit event is logged Given the same user pushes an ERP batch with total_amount equal to 25000 Then the submission enters status "Pending Approval" and no ERP call is made until approvals are granted Given an approval policy requiring at least two approvers: one Finance and one Budget Owner for the scope When both approvals are recorded within 72 hours Then the submission transitions to "Approved" and the ERP push executes successfully Given an approval request that is not fully approved within 72 hours When the SLA expires Then the submission transitions to "Expired" and must be resubmitted Given a user without push permission attempts to push any batch When the request is made Then the response status is 403 and the attempt is logged
SSO Group Mapping to Roles and Scopes
Given an IdP group "Finance_BU_A" mapped to role "Finance" with scope "Business Unit A" When a user is added to this group and authenticates Then their effective role is "Finance" scoped to "Business Unit A" within 5 minutes Given the same user is removed from the IdP group When they next authenticate or refresh a token Then their access to series line items is revoked within 5 minutes and subsequent requests return 403 Given a user with no mapped IdP groups When they authenticate Then their effective permissions are none and all access requests return 403 Given a user belongs to multiple mapped IdP groups When roles and scopes are resolved Then effective permissions are derived only from mapped groups with no implicit elevation beyond those mappings
Consistent Enforcement Across UI, API, Exports, and Connectors
Given a user with role and scope "X" When they access series line items via UI, REST API, CSV export, and ERP connector Then the set of accessible records and visible fields is identical across channels for the same filters and scope Given an unauthorized request for a restricted field via API When the request is made Then the response status is 403 and the response body includes error_code "RBAC_FORBIDDEN" with no field values returned Given an ERP connector using a service principal When it authenticates with assigned role and scope Then it is subject to the same RBAC and redaction rules as human users
Access Event Logging and Auditability
Given any view, export, or push attempt on series line items When the action occurs Then an audit event is persisted with fields event_id, user_id or client_id, role, scopes, action, resource_type, resource_scope_id, outcome, interface, request_id, ip_address, and timestamp (UTC) Given an Admin or Auditor queries audit logs by time range and user_id When up to 50,000 matching events exist Then the query returns results within 5 seconds at the 95th percentile Given stored audit logs When a user attempts to modify or delete an existing event Then the attempt is blocked and an alert is sent to security monitoring Given log ingestion is interrupted for more than 60 seconds When monitoring detects a gap Then an alert is emitted and visible in the admin dashboard
PII and Non-Essential Field Redaction Policy
Given the defined redaction policy for fields owner_full_name, owner_email, calendar_id, and internal_notes When an Auditor or Budget Owner views or exports series line items Then those fields are replaced with "[REDACTED]" in UI, API responses, CSV/JSON exports, and connector payloads Given a Finance or Admin user views or exports series line items When the request is authorized Then the same fields are returned with original values Given a client attempts to bypass redaction via query parameters, field selection, or sort on a redacted field When the request is processed Then the server ignores the bypass attempt, returns redacted results, and logs the attempt Given records containing nulls, unicode characters, or long strings in redacted fields When redaction is applied Then the output preserves schema and encoding, and no unredacted values are leaked in metadata, headers, or filenames

Spend Guardrails

Set monthly spend caps by org or team. Pre-activation checks flag when a new series would exceed the cap and suggest pausing low-value or dormant series instead. Approval workflows keep spending intentional while preserving agility for critical work.

Requirements

Monthly Spend Caps by Org/Team
"As a finance admin, I want to set monthly spend caps for the org and teams so that we can control costs without micromanaging every meeting."
Description

Enables administrators to define monthly spend caps at the organization and team levels with effective dates, default caps, and team-specific overrides. Caps integrate with TimeTether’s billing and scheduling engines to compute real-time utilization from active and pending meeting series. Supports configurable warning thresholds, proration for mid-month changes, and timezone-aware month boundaries. Provides role-restricted management, audit logging, and immediate propagation to pre-activation checks and dashboards to ensure spending stays intentional without blocking critical operations.

Acceptance Criteria
Org-Level Cap with Effective Date and Proration
Given a Billing Admin sets the organization monthly cap to $X with effective date D in org timezone TZ When D is within the current month and today >= D Then the effective cap for the current month equals X * (number_of_days_from_D_through_month_end_in_TZ / total_calendar_days_in_month_in_TZ), rounded to the nearest cent using banker’s rounding And the effective cap for subsequent months equals X And the cap appears on Billing > Spend Guardrails with amount, effective date, timezone, creator, and status Active When D is in a future month Then the current month cap remains unchanged and the scheduled change appears with status Scheduled When X < 0 or D is invalid Then the save is rejected with field-level validation errors and no changes are persisted When retrieved via API GET /v1/spend/caps/org Then the saved cap matches UI values (amount, effective date, timezone, status)
Team Override Cap with Default Inheritance
Given an organization default monthly cap of $X exists When a new team is created with no explicit override Then the team’s cap defaults to $X and is displayed as Inherited in the team guardrails view Given a team T has no override When a Billing Admin or Team Owner sets a team override cap of $Y effective date D in org timezone TZ Then T’s effective cap for the current month is prorated using the same formula as org-level proration and displayed as Override with the computed prorated amount for the month And from the next month forward, T’s cap equals $Y When an existing team override is removed Then the team reverts to the organization default immediately and the change is reflected in dashboards within 60 seconds When invalid values are provided (negative amount, invalid date, or unauthorized role) Then the change is rejected with specific error messages and no persistence occurs
Real-Time Utilization from Active and Pending Series
Given active and pending meeting series each have an expected monthly charge C_i When a series is created, modified (cadence/participants), paused, or canceled Then organization- and team-level utilization U updates within 10 seconds as U = sum(prorated C_i active during the org month in TZ) / effective cap for the same scope and month And the utilization value is identical (±0.01) across API, dashboards, and pre-activation checks When a pending series’ start date shifts into or out of the current month Then utilization recalculates accordingly within 10 seconds When a series is deleted Then its contribution is removed from utilization within 10 seconds
Configurable Warning Threshold Alerts
Given org-level warning thresholds are configured (e.g., 70%, 85%, 100%) and optional team-level thresholds When utilization crosses any threshold upward for a scope (org or team) Then an in-app alert and an email are sent to Billing Admins (and Team Owners for team scopes) within 60 seconds including: scope, current utilization %, cap amount, month-to-date spend, and a link to Manage Guardrails And duplicate alerts for the same threshold are suppressed until utilization drops below that threshold and crosses it upward again or 24 hours elapse, whichever occurs first When thresholds are updated Then subsequent alerts use the new threshold values without requiring a page refresh When retrieving notifications history Then threshold-crossing events are listed with timestamp, scope, threshold, utilization %, and recipients
Timezone-Aware Month Boundaries and DST Handling
Given the organization timezone TZ is configured When computing month start/end and proration Then the window is [00:00:00 TZ on the first day, 23:59:59 TZ on the last day], using calendar days in TZ regardless of DST transitions When TZ observes a DST change within the month Then proration uses the count of calendar days in TZ; the cap amount is not adjusted for the missing/extra hour When the organization timezone is changed mid-month at time t Then subsequent proration and utilization calculations use the new TZ, and month-to-date utilized amount before and after the change differs by no more than $0.01 And the timezone change is captured in audit logs with actor, previous TZ, new TZ, and timestamp
Role-Restricted Cap Management and Audit Logging
Given roles: Billing Admin (org caps), Team Owner (their team overrides), other roles (read-only) When an unauthorized user attempts to create/update/delete a cap or threshold Then the request is denied with 403 and no changes occur When a cap or threshold is created, updated, or deleted Then an audit entry is recorded with actor id, role, scope (org/team and team id), before/after values (amount, effective date, timezone, thresholds), timestamp (UTC), IP, and request id, and is retrievable in UI and via API within 2 seconds When viewing cap history for a scope Then a chronological timeline shows each version with effective windows and current status (Active, Scheduled, Archived)
Immediate Propagation to Pre-Activation Checks and Dashboards
Given a cap or threshold value is changed for a scope When the change is saved Then pre-activation checks and dashboards reflect the new values within 60 seconds without requiring user refresh Given a user attempts to activate a new meeting series that would push utilization over 100% for the relevant scope in the current month When the pre-activation check runs Then it flags Over Cap with the computed post-activation utilization %, shows the amount over, and displays at least 3 candidate series to pause (if available) prioritized by dormancy (≥21 days no attendance) or low attendance And the user can either submit for approval per the configured workflow or pause selected series to proceed under cap; users with Critical Ops permission may proceed while creating an approval record When any of these actions are taken Then utilization and dashboards update in real time (≤10 seconds) and the action is logged in audit
Pre-Activation Cap Check
"As a meeting organizer, I want the system to tell me if a new series will exceed our cap before activation so that I can adjust or seek approval without delays."
Description

Injects a budget impact step into the meeting series creation and modification flows that forecasts the incremental monthly cost of the series based on cadence, duration, and remaining days in the billing period. Compares the forecast to org and team caps to provide inline guidance: allow, warn, or require approval per policy. Presents clear impact deltas, cap utilization status, and alternative actions, including pausing or snoozing suggested low-value or dormant series. Offers one-click submit for approval and preserves smooth one-click invites when within guardrails.

Acceptance Criteria
Forecast Calculation and Proration Accuracy
Given an org billing timezone and billing period end are configured And a pricing model is configured for the team And a user creates a meeting series with cadence, duration (minutes), and start date/time When the pre-activation cap check runs Then remaining_occurrences_in_period are computed from start date/time to the billing period end inclusive, using the org billing timezone And forecast_incremental_cost = rate_per_minute * duration_minutes * remaining_occurrences_in_period And forecast_incremental_cost is rounded to 2 decimals using standard half-up rounding And if the start date/time falls after the billing period end, forecast_incremental_cost = 0 And team and org utilization deltas are computed as (current_spend + forecast_incremental_cost) / cap for each scope And the calculation completes within 300 ms at P95
Allow Path: Within Org and Team Caps
Given team and org caps and current spend are known And a forecast_incremental_cost has been computed And the policy defines warn and approval thresholds When (current_spend + forecast_incremental_cost) is less than or equal to both caps and below the warn threshold for both scopes Then the system displays status = Allow with a green indicator And shows current spend, forecast_incremental_cost, remaining cap, and post-activation utilization % for both team and org And one-click invites remain enabled with no additional steps And no approval UI is displayed And proceeding schedules the series and sends invites in a single click And an "allow_path_shown" telemetry event is recorded with scope that determined outcome
Warn Path: Nearing Cap Thresholds with Alternatives
Given team and org caps and policy thresholds are defined And a forecast_incremental_cost has been computed When post-activation utilization for at least one scope is >= warn_threshold and < approval_threshold (or <= cap if no approval band) per policy Then the system displays status = Warn with an amber indicator and identifies the triggering scope(s) And the user can proceed without approval in a single click And the panel shows: forecast_incremental_cost, post-activation utilization %, remaining cap, and the utilization that triggers the warning And at least 3 alternative actions are listed with estimated monthly savings each, computed using the same pricing and proration method And each suggestion shows series name, owner, last occurrence date, attendance rate, and estimated savings And the user can Pause or Snooze a suggestion in one click if they have permission; applying an action recalculates utilization and updates the banner within 1 second And proceeding without changes records a warning acknowledgement event
Require Approval Path: Exceeds Cap Policy
Given team and org caps and policy thresholds are defined And a forecast_incremental_cost has been computed When post-activation utilization for any scope exceeds its cap or meets the policy's approval threshold Then the system displays status = Requires approval with a red indicator and identifies the triggering scope(s) And the one-click invite action is replaced with a single "Submit for approval" action And submission creates an approval request containing requester, series details (cadence, duration, start), forecast_incremental_cost, team/org deltas, and suggested alternatives And while pending, the series is saved in a draft state and no invites are sent And approvers receive the request and can Approve or Deny; upon Approve, invites are sent automatically and the requester is notified within 5 seconds; upon Deny, the draft remains unsent and the requester is notified And all submit/approve/deny actions are audit-logged with timestamps and actor IDs
Alternative Actions Identification Quality
Given historical meeting analytics are available When generating suggestions for cost-saving alternatives Then a series is flagged Dormant if it had 0 attendees across the last 3 scheduled occurrences or its last attended occurrence was >30 days ago And a series is flagged Low-value if its average attendance rate across the last 4 occurrences is <50% or RSVP decline rate is >40% And suggestions are scoped to the user's team first; if savings are insufficient to meet the target utilization, org-level candidates are included with appropriate permission checks And each suggestion's estimated monthly savings is computed using the same pricing and proration logic as the forecast for the remaining billing period And actions requiring permissions (Pause/Snooze) are only enabled when the requester has rights; otherwise, the UI presents a "Request owner action" option And applying suggestions updates the displayed utilization and remaining cap within 500 ms at P95
Edit Flow: Pre-Activation Check on Modifications
Given an existing series with current cadence and duration And the user edits cadence and/or duration and/or start/end dates When the pre-activation check runs on the edit form Then incremental_delta_cost = forecast(new_params) - forecast(current_params) for the remainder of the billing period And Allow/Warn/Require approval logic is applied using incremental_delta_cost and current spend And a negative incremental_delta_cost (cost decrease) shows as a credit, updates utilization accordingly, and allows immediate save And a positive incremental_delta_cost may trigger Warn or Requires approval as per policy; save is blocked only when approval is required And the UI shows side-by-side before vs after cost, utilization %, and cap remaining deltas for team and org And recalculations occur within 300 ms at P95 after each parameter change
Dormant Series Detection and Suggestions
"As a team lead, I want TimeTether to suggest dormant or low-value series to pause so that we can free budget for higher-impact meetings."
Description

Automatically identifies low-value or dormant meeting series using signals such as attendance trends, frequent cancellations, no-shows, and long inactivity, weighted by team norms. Produces an explainable, ranked candidate list to pause, reduce frequency, or timebox, with predicted monthly savings. Integrates into pre-activation checks, the dashboard, and alerts, enabling one-click pause/snooze with owner notifications and reversible actions to maintain fairness and continuity.

Acceptance Criteria
Pre-Activation Cap Check Suggests Dormant Series
Given a team or org with a monthly spend cap and a proposed meeting series whose projected monthly spend exceeds the remaining cap, And the system has an up-to-date list of candidate dormant/low-value series with predicted monthly savings, When the owner attempts to activate the proposed series, Then the pre-activation screen presents a ranked list of at least 3 candidate series (or all if fewer) whose combined predicted savings are >= the cap shortfall, And each candidate displays last occurrence date, 12-week attendance trend, cancellation/no-show rates, confidence score, and recommended action (pause/reduce/timebox), And predicted monthly savings are shown in currency and sum correctly, And one-click actions (Pause, Snooze 30/60/90 days) execute successfully with p95 end-to-end time <= 5 seconds, And an audit log records user, action, series ID, timestamp, and before/after state, And all actions are reversible.
Dormant Series Ranking Quality and Explainability
- On a labeled validation set, Precision@10 >= 0.70, Recall@10 >= 0.60, and ROC AUC >= 0.80. - Each candidate provides the top 3 contributing signals with normalized weights summing to 100% and plain-language explanations. - Team norming: attendance and cancellation features are z-scored within team over a rolling 12-week window. - Predicted monthly savings = expected monthly occurrences × (estimated attendee-hours × org-configured blended hourly rate); backtest MAPE <= 10%. - Series with no activity for >= 8 weeks are tagged "Dormant" and included unless explicitly exempted.
Dashboard Candidate List with One-Click Actions
Given a user with Spend Guardrails:Manage permission, When they open the Spend Guardrails dashboard, Then a "Dormant or Low-Value Series" section is displayed with default sort by Rank (desc) and filters for Team, Owner, Action Type, and Min Savings, And each row shows: Series Title, Owner, Team, Next Occurrence (or "None"), Recommended Action, Predicted Monthly Savings, Confidence, and Key Signals, And clicking Pause or Reduce Frequency prompts a confirmation with estimated savings and impact; upon confirm, action success p95 server response <= 800 ms; owner and attendees are notified per preferences within 2 minutes, And Resume is available for paused items and restores prior schedule, participants, and settings exactly, And all list data and actions respect role-based access controls.
Alerting for Emerging Dormant Series
Given detection runs nightly at 02:00 in each team's local timezone, When a series crosses the dormancy threshold or enters the top-10 savings opportunities since the last run, Then an alert is queued and delivered to the series owner and team leads within 15 minutes, And a weekly digest every Monday at 09:00 local includes up to the top 10 candidates per team with total potential savings and one-click action links, And alerts honor user notification channels and quiet hours; opt-out changes are honored within 5 minutes, And alert links deep-link to the specific candidate with action context preloaded.
Reversible Pause and Fairness Continuity
- Pause action creates a reversible state for 30 days with a single-click Resume. - Resuming restores original recurrence rule, time window, participants, and fairness rotation state. - While paused, the series is excluded from fairness rotation; rotation credits accrue to the owning team and are applied to the next 3 occurrences upon resume. - If the next occurrence is < 24 hours away, the system requires explicit override to pause; otherwise Timebox is recommended by default. - Owner and attendees receive notifications within 2 minutes including reason and a resume link.
Team Norms Weighting and Threshold Configuration
- Admin can edit signal weights (attendance trend, cancellation rate, no-shows, inactivity) and the dormancy threshold in Detection Settings. - Inputs are validated: weights sum to 100% ± 0.5%; each weight 0–80%; threshold between 0.0 and 1.0 inclusive. - Preview simulates the top 50 candidates and total savings delta before saving; preview completes p95 <= 10 seconds. - Saved changes are versioned with editor, timestamp, and description; rollback to any prior version is available. - Changes apply to the next nightly detection run and to pre-activation checks within 5 minutes of save.
Multi-Level Spend Approval Workflow
"As a budget owner, I want a clear approval workflow for over-cap requests so that critical work can proceed with accountability."
Description

Provides configurable approval workflows for actions that would exceed a cap, supporting single or multi-level approvers by role (budget owner, team lead, finance) with SLAs, reminders, and escalations. Approvals can be granted, denied, or time-bound, with optional emergency overrides for critical work requiring justification. Approvers receive context including forecasted impact, affected teams, and suggested tradeoffs. All decisions are audit-logged, and successful approvals unblock scheduling and invitations without disrupting fairness rotations.

Acceptance Criteria
Role-Based Multi-Level Routing for Cap Exceedance
Given a user submits a series change that would exceed the configured monthly spend cap for a team When the request is created Then the system routes the approval sequentially to the configured roles in order: Team Lead -> Budget Owner -> Finance (if present) And each approver must explicitly Approve or Deny before the request advances And the next level is not notified until the prior level approves And a Deny at any level immediately marks the request as Denied and prevents scheduling And all approver assignments and decisions are recorded with timestamps and approver role
SLA, Reminder, and Escalation Timers
Given an approval step has an SLA of N hours configured When the SLA time elapses without action Then the approver receives an overdue reminder immediately and every M hours thereafter (per configuration) And after E hours past SLA, the request auto-escalates to the designated backup or next approver role And all reminders and escalations are captured in the audit log with delivery status And if escalation occurs, the original approver can no longer act unless re-assigned
Time-Bound Approval Window (Expiry)
Given an approver selects Approve with expiry and provides a duration or end date When the approval is saved Then the approval is valid only within the specified window And new spend-triggering actions within the window auto-pass that approval step; outside the window they require re-approval And the system notifies the requester and approvers 72 hours before expiry And upon expiry, future invitations created due to this approval are prevented from sending until re-approved And the audit log records the expiry parameters and notifications
Emergency Override with Justification and Post-Facto Review
Given a requester uses Emergency Override for critical work When the override is invoked Then the system requires a justification of at least 20 characters and selection of a criticality category And scheduling and invitations are unblocked immediately for the requested series And a post-facto approval task is created and routed to all required approver roles within 5 minutes And if post-facto approval is Denied, the system halts future invitations for the series and flags the series for review And all override details (justification, requester, timestamp, auto-unblock, post-facto outcomes) are audit-logged
Approver Context Package
Given an approver opens an approval request When the approval view loads Then it displays current cap, projected monthly spend delta, impacted org/team, affected series count, and earliest start date And it lists suggested tradeoffs (low-value or dormant series) with estimated savings and one-click pause links And it shows forecasted cap compliance for the next 3 months under Approve vs Deny And it confirms that fairness rotation will remain unchanged upon approval And the approver can download a CSV or PDF summary of the context
Audit Logging and Immutability
Given any event occurs in the approval workflow When the event is processed Then the system writes an immutable audit entry including request ID, actor, role, action (Submitted/Approved/Denied/Escalated/Overridden), justification (if any), timestamps (created, SLA deadlines, acted), and notification delivery results And audit entries cannot be edited or deleted via UI or API And audit logs are filterable by date range, team, approver role, and action And logs are exportable to CSV within 60 seconds for up to 10,000 entries
Unblock Scheduling Without Disrupting Fairness Rotation
Given a request receives final approval for all configured levels or an emergency override is used When the approval status becomes Effective Then the system immediately enables scheduling and sends invitations for the requested series And the fairness rotation algorithm configuration and participant rotation state remain unchanged from pre-request state And no participant’s after-hours burden increases beyond the baseline established prior to the request And if the request is Denied, scheduling stays blocked and the requester sees the denial reason and suggested tradeoffs
Cap Threshold Alerts
"As a budget owner, I want timely alerts about cap utilization and forecasted overages so that I can act before we hit the limit."
Description

Delivers proactive notifications when utilization crosses configurable thresholds (for example 70%, 90%, 100%) or when forecasts predict an overage date. Alerts target budget owners and impacted schedulers through Slack, email, and in-app banners, respecting quiet hours and user timezones. Messages include actionable links to view utilization, pause suggested series, or submit an approval request, keeping teams informed while minimizing notification noise.

Acceptance Criteria
Alert at 70% Utilization to Budget Owners
Given a monthly spend cap is configured for a team or org and utilization was below 70% in the last measurement When utilization reaches or exceeds 70% for the first time in the current cycle Then send an alert to all budget owners via their enabled channels (Slack, email, in-app) within 5 minutes And include current utilization percentage, cap amount, remaining budget, and a deep link to View Utilization for that scope And send no additional 70% alerts for that scope until utilization drops below 65% and crosses 70% again
Alert at 90% Utilization Includes Schedulers and Suggestions
Given a monthly spend cap is configured for a team or org and utilization was below 90% in the last measurement When utilization reaches or exceeds 90% Then send an alert to all budget owners and impacted schedulers via their enabled channels within 5 minutes And the alert includes top 3 suggested series to pause (by lowest value or dormancy), with a deep link to Pause Suggested Series pre-filtered to the scope And the alert includes a deep link to Submit Approval Request pre-filled with the scope and current utilization And send no additional 90% alerts for that scope until utilization drops below 85% and crosses 90% again
Urgent Alert at 100% Utilization With In-App Banner
Given a monthly spend cap is configured for a team or org and utilization was below 100% in the last measurement When utilization reaches or exceeds 100% Then send an urgent alert via all enabled channels within 2 minutes and display an in-app persistent banner on next page load And the alert and banner include current utilization, amount over cap if any, and deep links to View Utilization, Pause Suggested Series, and Submit Approval Request And suppress lower-severity threshold alerts (70%, 90%) for the current 12-hour window
Forecasted Overage Date Pre-Alert
Given a forecast model for the scope is available and updated within the last 24 hours When the model predicts a cap overage date within the current billing period Then send a forecast alert to budget owners and impacted schedulers via enabled channels within 10 minutes of the prediction update And the alert includes the predicted overage date, the projected utilization at period end, key drivers (e.g., active series count), and a deep link to the utilization forecast view And do not resend the forecast alert more than once per 24 hours unless the predicted overage date moves earlier by 2 or more days
Quiet Hours and Timezone-Adjusted Delivery
Given each recipient has a timezone and optional quiet hours configured When an alert is ready to send during a recipient’s quiet hours Then do not deliver Slack or email notifications to that recipient during the quiet period And queue the notification and deliver within 5 minutes after quiet hours end in the recipient’s local timezone And always render the in-app banner on next page load regardless of quiet hours
Notification Noise Control and Frequency Capping
Given multiple threshold events or forecast updates occur for the same scope within a 12-hour window When composing outbound notifications Then deduplicate by scope and channel and send only the highest-severity alert in that window And bundle multiple scopes for the same recipient into a single message per channel when possible, with a count and per-scope links And ensure no recipient receives more than one alert per scope per channel in a 12-hour window unless severity increases
Actionable Links, Delivery Fallbacks, and Audit Logging
Given an alert is sent for any threshold or forecast scenario When the message is delivered Then it contains three actionable deep links: View Utilization, Pause Suggested Series, Submit Approval Request, each resolving to the correct scope and loading within 2 seconds from a US region test network And if Slack delivery fails (non-2xx API response or timeout > 5 seconds), retry up to 3 times and fall back to email within 10 minutes And record an audit log entry with scope, threshold type, recipients, channels, send timestamps, delivery status, and link resolution results, retrievable via the admin audit API within 1 minute
Spend Utilization Dashboard
"As a product ops analyst, I want a dashboard of spend versus caps with forecasts so that I can plan and optimize our meeting portfolio."
Description

Provides a real-time dashboard showing current spend, cap, and utilization by organization and team, with drill-down to contributing series and their projected monthly costs. Includes trend charts, forecast scenarios, and filters by owner, team, and series status. Supports CSV export and embeds within TimeTether’s analytics area, enabling data-driven decisions about scheduling tradeoffs and guardrail tuning.

Acceptance Criteria
Real-time Spend and Utilization by Org and Team
Given an authorized user opens the Spend Utilization Dashboard When the dashboard loads Then it shows month-to-date Spend, Cap, and Utilization (%) at both Organization and Team levels And Utilization is calculated as (Spend ÷ Cap) × 100 and displayed to 1 decimal place And values auto-refresh at least every 60 seconds without a full page reload And the sum of Team-level Spend equals the Organization-level Spend within $0.01 And the displayed Cap matches the active configured monthly cap for each org/team
Drill-down to Contributing Series with Projected Monthly Costs
Given a team row is visible on the dashboard When the user selects the team row or clicks View details Then a drill-down view opens listing contributing series with columns: Series Name, Owner, Status, Projected Monthly Cost And Projected Monthly Cost is calculated using current schedule and pricing rules and shown in currency with 2 decimals And the list is sorted by Projected Monthly Cost descending by default And the sum of Projected Monthly Cost equals the team-level total within $0.01 And a Back action returns the user to the previous dashboard view and preserves scroll position and filters
Trend Charts and Forecast Scenarios
Given historical spend data exists When the user views the Trends section Then a chart shows monthly spend for the last 6 completed months and the current month-to-date And a forecast line displays end-of-month projection for the Status Quo scenario And a scenario selector allows switching between at least Status Quo and Cap −10% scenarios And changing the scenario updates the forecast values and chart in ≤1 second And chart tooltips show exact values (Spend, Cap, Utilization) for the hovered month/scenario
Filtering by Owner, Team, and Series Status
Given the dashboard and its filter panel are visible When the user applies filters for Owner (multi-select), Team (multi-select), and Series Status (Active, Paused, Dormant) Then all metrics, lists, and charts reflect the filters within 500 ms of data response And filter chips summarize active filters and can be removed individually And filters combine using AND logic across dimensions And clearing all filters resets the view to the unfiltered default And the URL updates to encode the active filters for deep-linking
CSV Export of Current View
Given a filtered or unfiltered view of the dashboard is displayed When the user selects Export CSV Then a CSV file downloads within 5 seconds containing the rows and columns of the current primary table (teams or series drill-down) And the first row contains headers and numeric values are plain numbers with 2 decimal places and no thousands separators And the sum of exported Projected Monthly Cost equals the on-screen total within $0.01 And the file name follows the pattern spend_utilization_YYYY-MM-DD.csv And exported rows respect all active filters and sort order
Embedded within Analytics Area and Navigable
Given the user navigates to Analytics in TimeTether When the user selects Spend Guardrails → Spend Utilization Then the dashboard renders within the Analytics layout with standard header and breadcrumbs And the route is shareable and restores state (active tab and filters) on reload And back navigation returns to the Analytics landing page And users without Analytics access see a 403/Not Authorized message and no data
Performance, Loading, Empty, and Error States
Given an organization with up to 200 teams and 10,000 series When the dashboard is opened under normal load Then initial above-the-fold content renders in ≤2 seconds on a 25 Mbps connection and p95 API latency is ≤800 ms And skeleton loaders are shown until data resolves and are removed once content renders And if no spend data is available, an empty state explains the condition and links to setup And if data retrieval fails, an error banner is displayed with a Retry action and no stale values are shown
Guardrail RBAC and API
"As a systems admin, I want RBAC and APIs for guardrails so that we can integrate with our finance stack and enforce the right controls."
Description

Implements role-based access controls for cap management, approvals, and pausing actions, mapping to existing SSO groups and TimeTether roles. Exposes secure APIs and webhooks to read utilization, submit approval decisions, and synchronize caps with external finance or procurement systems. Integrates with the billing provider for authoritative spend totals and ensures comprehensive audit logs for compliance and support.

Acceptance Criteria
RBAC Enforcement for Cap Management via SSO Mapping
Given a user authenticated via SSO whose group maps to the Finance Admin role, When they set an org-level monthly cap via UI or API, Then the request succeeds with 2xx and the cap is persisted and visible via GET /caps with correct scope and effective_date. Given a user in the Team Owner role, When they set a team-level cap within their team, Then the request succeeds with 2xx and is scoped to that team. Given a user without Cap Manager privileges (e.g., Viewer), When they attempt to create/update/delete any cap, Then the system returns 403 RBAC_FORBIDDEN and no state changes occur. Given an SSO group-to-role mapping is updated in the IdP, When the user reauthenticates, Then the user’s TimeTether role reflects the change within 5 minutes. Given any cap change occurs, Then an audit log entry records actor_id, role, source (UI/API), target_scope (org/team), previous_value, new_value, timestamp, and request_id.
Approval Decision Submission API
Given an approval request exists for a series that would exceed a cap, When a client with scope approvals:write POSTs /approvals/{id}/decision with an Idempotency-Key and payload {decision: approve|deny, reason}, Then the API responds 200, records the decision, updates status, and emits an approval.decision webhook. Given the same Idempotency-Key is retried, Then the API returns the original 200 response and does not duplicate the decision. Given a client lacks the required scope or role, When they POST a decision, Then the API returns 403 RBAC_FORBIDDEN. Given the payload is invalid, When submitted, Then the API returns 422 with validation errors. Given a denial decision is recorded, Then the series remains paused/uncreated and the requester is notified; Given an approval decision, Then the series is activated and scheduling begins within 2 minutes. Given any decision is made, Then an audit entry is written linking approval_id, decision, actor, reason, and timestamp.
Authoritative Spend Totals via Billing Provider
Given a pre-activation check for a new series, When computing projected monthly spend, Then the system fetches authoritative totals from the billing provider and uses those values for cap comparison. Given the billing provider is unavailable, Then the system falls back to cached totals not older than 15 minutes and marks decision_source=cached; if cache is older than 15 minutes, Then the system returns 503 RETRY_LATER and does not create or activate the series. Given the new series would exceed an active cap, Then the API/UI returns 409 CAP_EXCEEDS with a suggestions array containing candidate series to pause, each with last_activity_at and estimated_monthly_savings. Given a discrepancy >1% between local and provider totals is detected, Then the provider value is used and an alert is logged. Given the cap would not be exceeded, Then the request proceeds with 2xx and audit logs include provider_total, local_total, and projection inputs.
Secure Webhooks for Utilization and Approvals
Given a utilization change >5% or an approval lifecycle event occurs, Then a webhook event (spend.utilization.updated, approval.requested, or approval.decision) is POSTed to subscribed endpoints within 60 seconds. Given a webhook is delivered, Then the request includes an HMAC-SHA256 signature header over the raw body with tenant-specific secret and a timestamp, enabling verification; unsigned or invalid signatures are rejected by receivers. Given the receiver returns a non-2xx response, Then the system retries up to 8 times with exponential backoff and jitter; events are idempotent via event_id and can be safely retried. Given a webhook secret is rotated, Then both old and new secrets are accepted for up to 1 hour during rotation. Given an endpoint responds 410 Gone, Then the subscription is disabled and no further deliveries are attempted. Given an event is successfully delivered, Then its delivery status, attempts, and timestamps are visible via GET /webhooks/events.
Comprehensive Audit Logging and Export
Given any action related to caps, approvals, pausing/resuming series, or utilization reads via UI or API, Then an immutable audit record is written including actor_id (or service principal), role, sso_groups, action, target_scope, before/after values, timestamp (UTC, ms precision), ip, user_agent, request_id, and outcome. Given audit logs are queried via GET /audit, Then results can be filtered by time range, actor, role, action type, and target scope, and exported as JSON or CSV; secrets and tokens are redacted. Given system clocks experience skew, Then server-side timestamps remain monotonic and synchronized via NTP. Given retention policies, Then audit logs are retained for at least 12 months in a write-once store; deletions occur only per compliance with a tombstone entry. Given a support investigation requires traceability, Then related audit entries can be correlated via request_id across services.
Cap Synchronization API with External Finance Systems
Given an authorized client with scope caps:write, When it PUTs /caps/{scope}/{id} with amount, currency (ISO-4217), effective_date (UTC), and reason, Then the cap is upserted and GET /caps reflects the change within 30 seconds. Given concurrent updates occur, Then the API enforces optimistic concurrency via ETag/If-Match; mismatches return 409 VERSION_CONFLICT without persisting changes. Given listing caps, Then GET /caps supports pagination with limit and next_cursor and returns a stable ordering; on rate limits, 429 is returned with a Retry-After header. Given a batch upsert is submitted, Then partial failures return 207 Multi-Status with per-item results; successful items are committed and failed items are not. Given a currency mismatch with tenant settings without configured FX rates, Then the request is rejected with 422 INVALID_CURRENCY. Given any cap change via API, Then corresponding webhooks are emitted and audit entries are recorded.
RBAC for Pausing and Resuming Meeting Series
Given a Team Owner or Org Admin initiates a pause for a series, Then the system requires a reason code, applies the pause immediately, and GET /series shows status=paused within 30 seconds. Given a user without pause/resume permission attempts the action, Then the system returns 403 RBAC_FORBIDDEN and no changes occur. Given a series is marked critical, Then only Org Admins may pause it or an override requires an approval request, which triggers an approval.requested webhook. Given an authorized resume request is made, Then the series resumes scheduling; no backfill is scheduled unless explicitly requested via a backfill flag. Given pause/resume is invoked via API, Then idempotency via Idempotency-Key prevents duplicate state transitions. Given any pause or resume action occurs, Then notifications are sent to affected attendees according to notification preferences and are logged.

Billing Forecast

Model scenarios before you schedule: add/remove series, tweak cadence, and set start/stop dates to see projected charges. Compare plans side-by-side and lock predictable budgets aligned to actual usage patterns across quarters.

Requirements

Scenario Modeling Workspace
"As a budget-conscious team lead, I want to model multiple meeting series and cadences before scheduling so that I can see projected charges and avoid budget surprises."
Description

An interactive workspace that lets users add/remove meeting series, adjust cadence (weekly, biweekly, custom), set start/stop dates, and modify participant counts and time zones before scheduling. The workspace instantly calculates projected billable usage and charges, reflects organizational work windows and holiday calendars, and updates totals by month and quarter. It autosaves drafts, supports undo/redo, and integrates with the scheduling flow to convert an approved scenario into actual series with one click while preserving forecast assumptions.

Acceptance Criteria
Add/Remove Series with Instant Forecast Recalculation
Given an empty Scenario Modeling Workspace When I add a meeting series with cadence Weekly, duration 60 minutes, 6 participants across 3 time zones, start date 2025-10-01, and stop date 2025-12-31 Then projected billable usage and charges recalculate and display within 1 second (95th percentile) And the series appears in the scenario list with all configured parameters And monthly and quarterly totals reflect the added series When I remove that series Then usage, charges, and totals update within 1 second and return to their prior values
Cadence and Date Boundaries Handling
Given a series configured with cadence Biweekly (every 2 weeks) on Tuesdays and Thursdays at 09:00 in America/Los_Angeles, duration 45 minutes, start 2025-09-01, stop 2025-11-30 When the forecast is generated Then only occurrences on or after 2025-09-01 and on or before 2025-11-30 are included And the total number of occurrences matches the cadence rule across the window, accounting for DST changes And changing cadence to Custom RRULE "FREQ=WEEKLY;INTERVAL=3;BYDAY=MO" recomputes the occurrence list and totals within 1 second
Participant and Time Zone Changes Affect Forecast
Given a series with 8 participants across 4 time zones and displayed totals Usage=U and Charges=C When I increase participants to 10 and change one participant’s time zone from America/Los_Angeles to Europe/Berlin Then the workspace recomputes Usage and Charges within 1 second And the displayed values match the pricing service output for the same inputs
Work Windows and Holiday Calendars Reflected
Given organization work windows are 09:00–17:00 local time Monday–Friday and a US holiday on 2025-11-27 And a weekly 60-minute series with US and UK participants targeting Thursdays between 2025-11-01 and 2025-12-15 When the forecast is generated with constraints applied Then all forecasted occurrence times fall within the overlapping work windows for all participants and avoid organization holidays for any participant And any occurrences that cannot be placed within constraints are marked Skipped and not counted in usage or charges And monthly and quarterly totals reflect only placed occurrences
Monthly and Quarterly Totals with Fiscal Calendar Support
Given the fiscal year start month is set to April (4) And a scenario spans 2025-03-15 to 2025-05-15 When the forecast is generated Then monthly totals display for Mar 2025, Apr 2025, and May 2025 and equal the sum of occurrence charges in each month And quarterly totals display for FY Q4 2024 (Jan–Mar 2025) and FY Q1 2025 (Apr–Jun 2025) and equal the sum of their constituent months And changing the fiscal year start month to January recomputes quarter labels and totals within 1 second
Autosave, Restore, Undo, and Redo
Given I have made 5 changes to a scenario When I stop interacting for 2 seconds Then the scenario autosaves and a Save indicator confirms success When I reload the workspace Then the scenario state is restored exactly as last autosaved (series, parameters, and totals) And I can undo the last 5 actions one by one and see forecast and totals revert accordingly And I can redo those 5 actions to return to the autosaved state
Convert Scenario to Scheduled Series Preserving Assumptions
Given a scenario is marked Approved and includes two series with specific RRULEs, participant lists, time zones, and start/stop dates, and the forecast used a snapshot of organization work windows and holiday calendars dated 2025-09-03 When I click One-Click Convert Then the system creates scheduled series whose occurrence dates/times and participant assignments match the forecast snapshot And the scenario remains stored and is marked Converted with links to the created series And differences between current calendars and the snapshot are ignored during conversion to preserve forecast assumptions
Forecast Calculation Engine
"As a finance admin, I want accurate forecasts per month and quarter that reflect our pricing rules and real scheduling behaviors so that budgets align with actual usage."
Description

A deterministic engine that simulates recurring meetings over a chosen horizon, applying plan pricing rules, proration, overage thresholds, and discounts to produce month- and quarter-level projections. It accounts for fairness-driven rotation, time-zone shifts, daylight saving changes, holiday/blackout rules, and expected cancellations, aligning forecasts to the customer’s billing cycle and fiscal calendar. Outputs include per-series and aggregate breakdowns, annotated assumptions, and machine-readable results for downstream use.

Acceptance Criteria
Deterministic Reproducibility with Identical Inputs
Given identical inputs including series definitions, plan rules, billing cycle, fiscal calendar, holiday sets, forecast horizon, and a fixed cancellation policy/seed When the engine is executed twice under the same build Then the machine-readable JSON output is byte-identical with stable ordering And the metadata.hash value is identical across runs And no wall-clock timestamps are present; all times derive from inputs
Plan Proration and Mid-Cycle Changes
Given a billing cycle anchored on the 15th, a plan switch effective on the 24th within the same cycle, and a series added on the 20th and stopped on the 28th When the engine calculates charges for that cycle Then plan fees are prorated by exact billable days per rate and rounded to 2 decimals using half-even And per-series charges reflect the active plan on each applicable day And discounts are applied after proration and before overage And the cycle total equals the sum of the prorated components
Overage Thresholds, Tiers, and Quarterly Discounts
Given tiered overage thresholds at 100 and 250 billable units with rates 1.00, 0.80, and 0.60 respectively, rollover=false, and a quarterly volume discount of 5% when quarter total exceeds 800 units; month M has 275 units and quarter Q has 825 units When the engine computes charges for M and Q Then M overage equals (100*1.00) + (150*0.80) + (25*0.60) And the 5% discount is applied once to the quarter overage subtotal (Q), not to monthly subtotals And no rollover is applied because rollover=false And all overage calculations are annotated with thresholds and rates used
Timezone and DST-Aware Instance Simulation
Given a weekly series at 09:00 America/Los_Angeles with participants in Europe/Berlin, fairness rotation alternating host timezone every 2 occurrences, account timezone America/New_York, and a horizon spanning the US DST start in March When the engine simulates March Then occurrences follow the series RRULE in the series timezone with correct DST transitions And occurrences are assigned to billing periods using the account timezone boundaries And any occurrence outside defined work windows or within blackout rules is excluded from billable counts And assumptions include annotations for DST-affected weeks
Holiday/Blackout Rules and Expected Cancellations
Given a series with 10 occurrences in April, a holiday on 2025-04-15, a blackout window 2025-04-22 to 2025-04-23, and expected_cancellation_rate=10% with policy=drop-last-in-period When the engine simulates April Then occurrences on 2025-04-15 and within 2025-04-22 to 2025-04-23 are cancelled And 1 additional occurrence is removed per the policy, deterministically And annotations include cancelled_count=3 and reasons [holiday, blackout, expected_cancellation]
Billing Cycle and Fiscal Calendar Alignment
Given a billing cycle anchor on day 10 and fiscal quarters starting July 1 (Q1=Jul-Sep) When the engine produces month and quarter rollups for Jun through Oct Then monthly ranges are [May10-Jun09], [Jun10-Jul09], [Jul10-Aug09], [Aug10-Sep09], [Sep10-Oct09] And quarter ranges are [Jul01-Sep30] And month and quarter totals equal the sum of included periods with no overlaps or gaps And labels and date ranges in the output match these anchors
Per-Series and Aggregate Outputs with Machine-Readable Schema
Given a forecast request with 3 series and 2 plans for comparison and schema version v1.0.0 When the engine returns results Then the JSON conforms to schema v1.0.0 with required fields: forecast_id, as_of, currency, series[], aggregate, assumptions[], plan_comparisons[] And as_of echoes the request input And each series item contains series_id, plan_id, period_breakdown[], unit_counts, charges And aggregate period totals equal the sum across series within 0.01 currency units And plan_comparisons include per-period absolute and percentage deltas and a 12-month total delta And all datetimes are ISO 8601 with timezone offsets and amounts are rounded half-even to 2 decimals
Plan Comparison Side-by-Side
"As an account owner, I want to compare plans side-by-side against my scenario so that I can select the most cost-effective option."
Description

A comparison view that evaluates the current plan against alternative tiers (monthly vs annual), showing projected charges, overage likelihood, break-even points, and total cost of ownership over the selected horizon. It highlights the best-value plan based on scenario inputs, surfaces deltas and savings, and includes toggles for taxes, currency, and billing frequency. Users can select a plan from the comparison and attach it to the scenario for approval.

Acceptance Criteria
Render Side-by-Side Comparison with Core Metrics
Given a saved billing forecast scenario with a selected horizon When the Plan Comparison view is opened Then the current plan and all available alternative tiers (monthly and annual) are displayed in side-by-side columns (minimum 3 columns) And each plan column shows projected charges per billing period, total cost of ownership over the selected horizon, overage likelihood (%), and break-even point And all monetary values use the currently selected currency and billing frequency And metrics reflect the latest scenario inputs with a last computed timestamp And the view renders with complete data within 2 seconds on a broadband connection
Billing Frequency Toggle Updates Forecast
Given the comparison view is visible and a billing frequency toggle is present When the user switches from Monthly to Annual (or vice versa) Then projected charges, total cost of ownership, overage likelihood, and break-even point recompute using the catalog’s term pricing and discounts And annual pricing applies term discounts and proration rules as defined in the plan catalog And the selected billing frequency persists to the scenario state And the recomputation completes and updates all columns within 2 seconds
Taxes and Currency Toggles Recalculate Amounts
Given the comparison view is visible and the taxes toggle is OFF When the user turns taxes ON Then a tax line item and tax-inclusive totals appear for each plan using the organization’s configured tax rate for the selected region And turning taxes OFF removes the tax line item and reverts totals accordingly Given the currency selector is set to USD When the user switches currency (e.g., USD to EUR) Then all monetary amounts convert using the latest available FX rate from the configured source as of the current date And the currency code/symbol updates and amounts round to 2 decimals with consistent rounding rules And a tooltip shows the FX rate and timestamp used And all updates complete within 2 seconds
Best-Value Plan Highlight Logic
Given multiple plans are displayed with computed totals and metrics When determining the best-value plan Then the plan with the lowest total cost of ownership over the selected horizon (including taxes if enabled and term discounts) is marked with a “Best Value” badge And if two or more plans are within 1% TCO of each other, the plan with the lower overage likelihood is selected And if still tied, the Annual term variant is selected And only one plan is highlighted at any time And the badge updates immediately after any scenario input or toggle change
Delta and Savings Presentation
Given the current plan and alternative plans are visible When the comparison view renders Then each alternative plan shows deltas versus the current plan: total cost of ownership difference (absolute and %), overage likelihood delta (percentage points), and break-even shift (days or date) And savings are displayed in green with a positive indicator and cost increases in red with a negative indicator And hovering or tapping a delta reveals the formula and inputs used And deltas update consistently after any change to scenario inputs or toggles
Select and Attach Plan to Scenario for Approval
Given the user has Editor permissions on Billing Forecast scenarios and the comparison view is visible When the user selects a plan and clicks “Attach to Scenario” Then the scenario stores the selected plan id, term (monthly/annual), currency, taxes toggle state, selected horizon, and computed total cost of ownership And the scenario displays the attached plan in its summary with a link back to the comparison And a success confirmation is shown and an audit log entry is recorded with user, timestamp, and plan details And selecting a different plan replaces the attachment after user confirmation And users without sufficient permissions see a clear error and no attachment is made
Dynamic Recalculation on Scenario Usage Changes
Given the user modifies scenario usage inputs (e.g., adds/removes series, adjusts cadence, or changes start/stop dates) When the changes are applied or saved Then the comparison view recalculates projected charges, total cost of ownership, overage likelihood, break-even points, deltas, and best-value highlight based on the new inputs And recalculation completes within 3 seconds and reflects in all displayed plans And a new computation timestamp is shown and the prior comparison snapshot is versioned for auditability And if recalculation fails, an error state is shown with a retry option and no stale values are persisted
Budget Locks and Guardrails
"As a budget owner, I want to set and enforce quarterly caps so that scheduling respects our budget without manual policing."
Description

Controls that allow setting quarterly and annual budget caps tied to a forecast, with soft and hard thresholds. During scheduling, the system warns when a scenario exceeds caps, blocks creation if hard limits are enabled, or requests admin approval. Includes one-click commitment to a plan that matches the forecast, automated overage warnings, and clear messaging about the impact of changes to cadence, duration, or participants on projected spend.

Acceptance Criteria
Set Quarterly and Annual Budget Caps from Forecast
Given an active forecast with quarterly and annual projected charges When an Admin sets quarterly and annual budget caps and clicks Save Then the caps are persisted against the forecast version and fiscal periods And the caps are visible in the Billing Forecast UI within 2 seconds And only Admins can create, edit, or delete caps; non-admins see read-only values And an audit log entry records user, old/new values, timestamp, and forecast version
Soft Cap Warning During Scenario Scheduling
Given soft budget thresholds are enabled for the forecast And the user is modeling a scenario in Billing Forecast When the projected spend for any quarter or the year exceeds the corresponding cap Then a non-blocking warning is shown before scheduling And the warning displays the overage amount and percent by period And the user can proceed to schedule without admin approval And the event is logged with scenario ID, cap values, and overage amounts
Hard Cap Enforcement Blocks Scheduling
Given hard budget limits are enabled for the forecast When the user attempts to schedule a scenario whose projected spend exceeds any cap Then scheduling is blocked with a modal explaining which caps are exceeded And the Create action is disabled until the scenario is within caps or approval is granted And no calendar events or series are created And the API returns HTTP 403 with an error code per exceeded cap
Admin Approval Flow for Over‑Budget Scheduling
Given the organization requires admin approval when limits are exceeded When a user submits an approval request from the blocked scheduling modal Then an approval request is created with scenario snapshot and overage details And designated Admins receive notifications via email and Slack within 1 minute And Admins can Approve or Reject from the notification or app And on approval, scheduling proceeds automatically and the block is lifted And on rejection, the user is notified with reasons and no scheduling occurs And the full approval decision trail is available in the audit log
One‑Click Commitment to Forecast‑Matched Plan
Given a forecast is finalized and within approved caps When an Admin clicks Commit to Plan Then the system creates a locked plan matching forecasted cadence, duration, and participants And the plan's budget caps are bound to the corresponding fiscal periods And the commitment state is visible in the UI and via API And any attempt to diverge from the committed plan displays projected spend deltas before apply
Automated Overage Alerts and Impact Messaging on Changes
Given a committed or draft plan with caps and notification preferences configured When a change to cadence, duration, or participants increases projected spend beyond a soft or hard cap Then a pre-apply impact panel shows per-period delta amounts and percentages And upon saving, overage alerts are sent to stakeholders via configured channels within 5 minutes And alerts include current caps, new projection, variance, and required action (proceed, approval, or block) And for hard caps, changes are not applied until approval is granted or the scenario is adjusted within caps
Scenario Save, Share, and Audit
"As a program manager, I want to save and share forecast scenarios with stakeholders so that we can review and approve before scheduling."
Description

Capability to save named forecast scenarios, track versions, and generate diffs between revisions. Supports role-based sharing with comment threads and approval states (Draft, Pending Approval, Approved), plus an audit log capturing changes to series, assumptions, and selected plans. Provides exports (CSV/PDF) and a shareable link for stakeholders without full product access.

Acceptance Criteria
Save Named Scenario with Versioning
Given an Editor in a workspace, when they save a new forecast with a unique name (3–80 chars; letters, numbers, spaces, - and _), then Scenario v1 is created with owner, timestamp, and optional save note within 2 seconds. Given an existing Scenario, when an Editor saves changes, then a new immutable version is created with version number incremented by 1 and prior versions remain readable. Given a duplicate scenario name in the same workspace, when save is attempted, then the system blocks save with a validation message and suggests an available variant. Given two edits to the same base version, when a second save is received, then the server returns a 409 version conflict and prompts the user to review the latest version before retrying. Given a Scenario, when viewing version history, then each entry shows version number, author, timestamp, note, and approval state.
Generate Diff Between Versions
Given two versions of the same Scenario are selected, when a diff is requested, then added/removed/modified series are listed with before/after for cadence, start date, stop date, quantity, assumptions, and selected plans within 3 seconds for up to 200 series. Given no differences exist, when diff is generated, then the UI displays “No changes” with zero deltas. Given non-consecutive versions are selected, when diff is generated, then the comparison runs directly between the two chosen versions. Given the diff view, when the user filters to “only changes,” then unchanged items are hidden and totals reflect only changed items. Given a modified series in the diff, when expanded, then field-level changes display old value, new value, and per-quarter projected billing deltas.
Role-Based Sharing Permissions
Given a Scenario, when an Owner grants roles (Editor, Commenter, Viewer) to users by email in the Share dialog, then invitations are sent and roles persist; non-Owners cannot modify sharing. Given a user’s role, when they attempt an action, then permissions are enforced: Owner (all), Editor (create/edit versions, submit for approval), Commenter (comment only), Viewer (view only); unauthorized actions return 403 and UI controls are disabled. Given a user is removed from sharing, when they attempt to access the Scenario, then access is denied immediately and active sessions are invalidated. Given sharing changes are saved, when viewing the audit, then an entry records actor, affected users, roles added/removed, and timestamp.
Comment Threads and Mentions
Given a Scenario or diff view, when a Commenter or higher posts a comment with @mentions, then the comment appears in a thread and mentioned users receive a notification within 2 minutes. Given an inline comment anchored to a series or field change, when the underlying item changes in a new version, then the comment remains attached to the prior version and links to the new version context. Given a comment author, when they edit or delete their comment within 15 minutes, then the change is saved and an edit history is retained; after 15 minutes only Owners can delete with a required reason. Given a thread, when marked resolved by Commenter or higher, then its status updates to Resolved and the unresolved count decreases; resolved threads can be reopened. Given a Viewer role, when attempting to comment, then the action is blocked with a message explaining required permissions.
Approval Workflow Enforcement
Given a Draft version, when an Editor submits for approval with an optional note, then the version state changes to Pending Approval and designated approvers are notified. Given a Pending Approval version, when an Owner approves it, then the state changes to Approved, the approver and timestamp are recorded, and the version becomes read-only. Given an Approved version, when an Editor makes changes, then a new Draft version is created and the approval state resets; the Approved version remains unchanged. Given a Pending Approval version, when an Owner rejects it with a required reason, then the state returns to Draft and the reason appears in activity history. Given any user without approval rights, when attempting to approve or reject, then the action is denied with 403.
Audit Log Completeness
Given any change to series, cadence, start/stop dates, assumptions, selected plans, sharing, approvals, comments, exports, or link settings, when the change is saved, then an immutable audit entry records actor, UTC timestamp, action, and before/after values. Given audit entries, when filtering by actor, action type, version, or date range, then results return within 1 second for up to 10,000 entries. Given audit entries under 24 months old, when queried, then they are retrievable. Given an Owner views an audit entry, when expanded, then the full field-level before/after payload is visible without truncation up to 10 KB per entry.
CSV/PDF Exports and Shareable Link Access Controls
Given a selected version or diff, when a Viewer or higher exports to CSV or PDF, then the file generates within 30 seconds for up to 200 series and includes scenario name, version(s), actor, timestamp, and “External” watermark if generated from a share link. Given an export that exceeds 30 seconds, when requested, then the system queues the job and emails a secure download link within 5 minutes. Given an Owner creates a shareable link, when scope (specific version or diff), expiration (1–365 days), and optional password are set, then a tokenized URL is generated that grants view-only access without sign-in and requires the password if set. Given a shareable link is revoked or expired, when accessed, then the system returns 410 Gone and records the attempt in the audit log. Given access via a valid share link, when viewing, then only the permitted version/diff is visible, editing controls are hidden, and all link accesses are logged with IP and timestamp.
Assumptions and Sensitivity Controls
"As a finance analyst, I want to tune assumptions and see sensitivity ranges so that I understand risk to our budget."
Description

A panel to configure key assumptions such as attendance rate, expected cancellations, growth in number of series, holiday impact, and working-hour constraints by region. The forecast displays sensitivity bands (e.g., conservative/base/aggressive) and confidence indicators derived from historical usage. Includes presets based on the organization’s past behavior and quick toggles to test what-if changes.

Acceptance Criteria
Attendance Rate Control Applies to Forecast
Given an open Billing Forecast with default assumptions When the user sets Attendance Rate to 75% and clicks Apply Then projected attendee count and total projected charges update within 1 second to reflect 75% attendance and a delta badge shows the % change vs previous Given the Attendance Rate input When a value < 10% or > 100% or non-numeric is entered Then an inline validation message appears and Apply is disabled Given a saved scenario When the user reloads the page Then the last saved Attendance Rate value is restored
Expected Cancellations Assumption Reduces Charges
Given a base forecast When Expected Cancellations is set to 20% Then the number of chargeable meetings in the period decreases by 20% ± 1 rounding tolerance and total charges decrease proportionally Given the Expected Cancellations input When a value outside 0%–100% is entered Then validation prevents apply and explains the allowed range Given Expected Cancellations is changed and applied When the user navigates away and returns to the scenario Then the configured value persists and the forecast remains consistent with it
Series Growth Rate Configuration
Given a scenario with quarterly growth enabled When the user sets Series Growth Rate to 10% per quarter and the forecast horizon is 2 quarters starting next quarter Then the forecasted active series count increases by 10% each quarter (compounded) and projected charges reflect the increased series volume Given the growth rate input When the value is between -100% and +300% Then Apply is enabled; otherwise an inline error appears and Apply is disabled Given a series cadence is changed from weekly to bi-weekly in the assumptions When growth is applied Then the projection recalculates using the new cadence and displays updated event and charge counts
Holiday Impact by Region
Given regions US and DE are selected and Holiday Impact is enabled When the forecast spans dates that include public holidays for those regions Then meetings falling on those holidays are excluded from chargeable counts and a per-region summary shows “X events removed” Given Holiday Impact is toggled off When applied Then previously excluded meetings are restored to the forecast and the removal summary is hidden Given the holiday impact info tooltip When opened Then it lists the holiday calendar sources used and the forecast date range considered
Working-Hour Constraints by Region
Given region APAC has a constraint 09:00–17:00 local time and region EU 08:00–16:00 When constraints are applied Then the forecast counts only meetings within those windows, shows a per-region after-hours % metric, and updates total projected charges accordingly Given a constraint window where start time is equal to or later than end time When the user attempts to apply Then validation prevents saving and explains the required HH:MM format and logical ordering Given constraints are saved in a scenario When the page is reloaded Then the same regional windows are restored and reflected in the forecast
Sensitivity Bands and Confidence Indicator Display
Given historical usage of at least 6 months When the forecast is displayed Then three lines labeled Conservative, Base, and Aggressive are shown with a shaded band between Conservative and Aggressive and numeric totals for each Given available historical usage When its length is 6+ months Then the confidence indicator shows High; when 3–5 months it shows Medium; when <3 months it shows Low, and the tooltip states “Based on X months of history” Given any assumption value is changed and applied When the chart refreshes Then all three lines recompute within 1 second and the confidence indicator updates if the historical window changes
Presets and What-If Toggles
Given the Assumptions panel When the user selects the Past Behavior preset Then attendance, cancellations, growth, holiday, and working-hour fields auto-populate from the organization’s last 2 quarters and a banner shows the preset name and timestamp Given a Custom Preset named “Q4 Budget Cap” is saved When the user reloads the page Then the preset appears in the presets list and applying it restores all fields exactly as saved Given quick toggles for Include Holidays and Apply Working-Hour Constraints When toggled on or off Then the forecast updates within 1 second and the toggle state is reflected in summary chips; when Reset All is clicked, all fields revert to Base defaults
Deviation Alerts and Reforecast
"As an admin, I want to be notified when actual usage deviates from forecast so that I can adjust plans proactively."
Description

Background monitoring that compares actual usage against the locked forecast and sends alerts (email/Slack) when variance thresholds are crossed. Provides one-click reforecast using the latest usage signals, suggests plan adjustments, and links back to the original scenario for traceability. Includes a summary of variance drivers such as new series, cadence changes, or unexpected after-hours meetings.

Acceptance Criteria
Variance Threshold Alert Triggering
- Given a workspace with a locked billing forecast for the current quarter and variance thresholds configured (percent and absolute) - And background monitoring is enabled - When the 7-day rolling projection of charges deviates from the locked forecast by >= the configured percent OR >= the configured absolute amount - Then an alert event is generated within 5 minutes of detection - And the alert payload includes workspace, scenario id/name, forecast period, baseline forecast amount, current projection, absolute variance, percent variance, threshold values, timestamp, and environment - And the alert includes a unique alert key to deduplicate repeated notifications - And the alert is not resent to the same channel for the same variance band within 24 hours unless variance increases by >= 5 percentage points or the absolute variance increases by >= the configured absolute threshold - And the event is persisted to an immutable audit log with status and delivery attempts
Multi-Channel Alert Delivery and Preferences
- Given a generated variance alert event - When delivery is attempted to Slack and Email recipients defined in Billing settings - Then a Slack message is posted to the configured channel with summary fields and a one-click Reforecast button - And an email is sent with subject "TimeTether Billing Variance Alert: [Scenario] [±X%/$Y]" and contains equivalent content and CTA - And per-user notification preferences (mute, channel choice, quiet hours) are respected - And delivery failures are retried up to 3 times with exponential backoff and are visible in admin logs - And alerts render correctly on desktop and mobile clients (Slack and major email clients) - And if a channel is misconfigured, the system falls back to the remaining channels and flags configuration errors
One-Click Reforecast from Alert
- Given a valid alert containing a signed reforecast link that expires in 24 hours - And the user has Billing:Manage permission for the workspace - When the user clicks the link - Then the system computes a fresh forecast using the latest usage signals through the prior hour (series adds/removals, cadence changes, after-hours rates) - And displays a diff view versus the locked forecast by month and quarter, including absolute and percent changes - And no changes are applied until the user confirms "Apply Reforecast" - And on confirmation, the reforecast is saved as a new version linked to the original scenario, with an option to set it as the new locked baseline - And an audit record captures actor, time, source alert id, inputs, and resulting forecast id - And if the link is expired or permissions are insufficient, a clear error is shown and no computation is applied
Plan Adjustment Recommendations after Reforecast
- Given a completed reforecast - When generating plan recommendations - Then at least two options are produced (current plan vs recommended plan) with projected charges, overage risk, and savings/cost deltas - And recommendations honor configured budget guardrails and contract constraints (minimum term, notice periods, plan family) - And pricing math reconciles to within $0.01 of the rate card for each option - And the user can accept a recommendation to create a plan change proposal with an effective date and required approvals - And the recommendation includes a short rationale referencing usage patterns (e.g., increased after-hours meetings, new recurring series) - And the proposal links back to both the reforecast and the original scenario for traceability
Variance Driver Summary in Alerts
- Given a variance alert triggered by deviation from the locked forecast - When composing the variance summary - Then the alert includes top drivers across: new series count, cadence changes, after-hours meetings above baseline, participant/timezone shifts, and cancellations - And each driver lists its estimated contribution in dollars and percentage points - And the sum of driver contributions reconciles to the total variance within 1 percentage point or $10 (whichever is greater) - And each driver includes a deep link to a pre-filtered usage report showing the underlying events - And the baseline for after-hours comparisons is the assumption set from the locked forecast scenario
Traceability to Original Forecast Scenario
- Given a locked forecast scenario exists - When alerts and reforecasts are created - Then each artifact stores and displays a pointer to the original scenario id/name and lock timestamp - And the UI provides a link back to the original scenario details from the alert and reforecast views - And the original scenario remains immutable; any changes create a new version with a distinct id - And side-by-side comparison views show Original vs Current Reforecast with version numbers and timestamps - And exported CSV/PDF of the alert or reforecast includes the original scenario reference for audit purposes

Fatigue Radar

A predictive heatmap that surfaces next‑sprint after‑hours hotspots by person, team, and region. It explains the drivers (specific series, days, and time windows) so you can target fixes before burnout clusters form, reducing after‑hours and surprise no‑shows.

Requirements

Unified Calendar & Series Ingestion
"As a scheduling ops admin, I want TimeTether to reliably ingest and normalize all recurring meetings and availability so that Fatigue Radar has accurate, complete data to forecast after-hours risk."
Description

Continuously ingest and normalize recurring meeting data from connected calendars (Google Workspace, Microsoft 365), including series definitions (RRULE), instances, organizers, participants, time zones, durations, and locations. Enrich events with team, role, and region metadata; import PTO, public holidays, and planned travel where available. Resolve duplicates and cross-calendar overlaps, handle time zone conversions, daylight saving shifts, and retroactive edits. Maintain change history and incremental syncs with retry/backoff and integrity checks. Persist a clean, queryable data model optimized for forecasting and heatmap queries. Integrates with TimeTether’s existing OAuth connectors and entity directory to provide a reliable, complete foundation for Fatigue Radar predictions and explanations.

Acceptance Criteria
Multi-Provider RRULE Series Normalization
Given connected Google Workspace and Microsoft 365 accounts with recurring series using RRULE, EXDATE, and modified instances When the ingestion job runs (initial or incremental) Then the system persists a normalized Series and Instance model with fields: series_id, provider, provider_event_id, organizer_id, participant_ids, timezone, start_at_utc, start_at_local, end_at_utc, duration_minutes, location, recurrence_rule, exceptions And instance count for the next 12 weeks exactly matches provider-generated instances (difference = 0) And modified/cancelled occurrences are stored with status and last_seen_at timestamps and reflect provider changes within 5 minutes
Time Zone and DST Correctness
Given series anchored in America/New_York and Europe/Berlin that cross DST transitions When instances are generated and stored Then each instance start_at_local equals the provider’s local wall-clock start time for that date and timezone And utc_offset_at_start matches the provider’s offset on that date And no instance shifts day-of-week relative to the provider across DST boundaries And converting start_at_utc back to local time round-trips with zero-minute drift
Duplicate and Overlap Resolution Across Calendars
Given a user has duplicate invites across Google and M365 for the same meeting When ingestion detects events with the same external_uid OR matches on organizer + meeting_url OR overlapping start windows (<= 5 minutes) and overlapping participants Then one canonical meeting_id is persisted with linked duplicate_ids, and only the canonical instance is included in downstream metrics And re-running ingestion for the same time window is idempotent (no new duplicates created) And canonical selection priority is external_uid match > meeting_url match > provider priority (Google over M365), and the chosen rationale is recorded
Directory and Regional Metadata Enrichment
Given the entity directory maps users to team_id, role, region, and work-window preferences When events are ingested Then each participant is resolved to an entity_id, and the event is enriched with team_ids, roles, regions; unresolved participants are tagged unknown with reason And public holidays, PTO, and planned travel feeds are ingested and stored as availability blocks linked to entity_id with source and date range And events overlapping availability blocks are flagged with overlap_minutes computed for conflict analysis
Incremental Sync, Retry, and Change History
Given webhooks and scheduled polling are enabled for providers When provider changes occur (edit series, cancel occurrence, retroactive past edit) Then incremental sync processes deltas end-to-end within 5 minutes and advances per-account checkpoints/cursors And transient errors (HTTP 429/5xx) trigger exponential backoff (min 1s, max 5m, jitter) with up to 6 retries before alerting And all applied changes write audit history with before/after hashes, actor/source, applied_at, and correlation_id; replaying the same change is idempotent And webhook and poll signals for the same change are de-duplicated via correlation_id
Queryable Model and Performance for Heatmaps
Given the persisted model for Fatigue Radar When executing standard heatmap queries (after-hours minutes by person/team/region for next sprint and last 90 days) Then p95 latency is <= 400 ms for warm/cached slices and <= 2 s for cold queries on datasets up to 50k users and 5M instances And results match provider ground truth within ±1 minute per instance and ±0.5% aggregate variance And required indexes/materializations exist: person_id+time_bucket, team_id+time_bucket, region+time_bucket; query plans use these indexes (>95% of executions)
OAuth Integration and Permission Respect
Given user consent is granted via TimeTether OAuth connectors with minimum required scopes When calendars are connected, refreshed, or revoked Then only authorized calendars are ingested; tokens are stored/rotated securely; revocation halts ingestion within 10 minutes and clears queued tasks And calendar ACLs are enforced: private event fields are redacted and stored as null with visibility=redacted; only metadata allowed by scope is persisted And access and data operations are audit-logged with user/account, scope, purpose; export/delete requests are processed within 30 days of receipt
Work Window & After‑Hours Policy Engine
"As an engineering manager, I want after-hours rules that reflect each region’s and person’s actual working hours so that hotspots are flagged accurately and fairly."
Description

Provide a configurable rules engine that determines per-user effective work windows and after-hours thresholds, with support for team defaults, regional calendars, flex schedules, exceptions, half-days, and effective-dated changes. Compute after-hours flags and penalty scores for each meeting instance, factoring commute buffers, preferred core hours, and on-call rotations. Expose a reusable service that standardizes after-hours classification across Fatigue Radar, scheduling suggestions, and fairness rotation logic. Ensures hotspots are flagged consistently and fairly, reflecting real-world working patterns.

Acceptance Criteria
Team Default Inheritance with User Overrides
- Given a team default work window of 09:00–17:00 in America/Los_Angeles and a 15-minute commute buffer before/after the window, when a user has no personal settings, then meetings starting at 08:55 or ending at 17:20 are classified as after-hours and meetings fully between 09:00–17:00 are not. - Given the same team defaults, when the user sets a personal work window of 10:00–18:00 with preferred core hours 11:00–15:00, then the engine uses 10:00–18:00 for classification and ignores the team default for that user. - When the team default changes, then users with personal overrides retain their personal effective window and classification remains unchanged for them. - Given a meeting at 09:45 PT (15 minutes before the user’s 10:00 start), then the after-hours flag is true and a non-zero penalty is returned; given a meeting at 12:00 PT, then the after-hours flag is false and the after-hours penalty is zero. - All classifications are timezone-correct using IANA zones and are deterministic for the same inputs.
Effective-Dated Changes and Historical Consistency
- Given a user window of 10:00–18:00 effective through 2025-09-14 and a new window of 08:00–16:00 effective 2025-09-15, when evaluating a meeting on 2025-09-12 17:30 local, then it is not after-hours; when evaluating 2025-09-16 17:30 local, then it is after-hours. - When an effective-dated change is saved, then only meetings on/after the effective date are reclassified; historical meeting classifications before the effective date remain unchanged. - When re-running classification for past instances after a new rule takes effect, then results for dates prior to the effective date match previous outputs (idempotent replay). - Changes are recorded with effective_start, created_by, and created_at; audit logs can be queried to show which rule version produced a classification for any given meeting instance. - When a rule is scheduled for a future date, then the preview endpoint returns both current and post-change classifications for a supplied series without altering stored historical data.
Regional Calendars, Holidays, and Half-Days
- Given region Europe/Berlin with a half-day on 2025-12-24 (working window 09:00–13:00) and 15-minute buffers, then a meeting at 11:30–12:00 is not after-hours and a meeting at 14:00–14:30 is after-hours. - Given a full public holiday on 2025-12-25, then any meeting on that date is after-hours for affected users regardless of team defaults. - When DST transitions occur, then local wall times (e.g., 09:00–17:00) are preserved using IANA conversion; the same local meeting time before/after the shift yields consistent classification relative to the stated window. - Regional calendar precedence is: user exception > user window > team default > regional default; classification follows this order deterministically. - When a regional calendar is unavailable, then the service falls back to team defaults and returns a metadata warning code without failing the request.
Flex Schedules and One-off Exceptions
- Given a user flex schedule (Mon–Wed 08:00–16:00, Thu 12:00–20:00, Fri off) and an exception on Tue 2025-10-07 14:00–16:00 (unavailable), then a meeting at Tue 2025-10-07 15:00–15:30 is after-hours while Tue 2025-10-08 15:00–15:30 is not. - Given a recurring series every Tuesday 15:00–15:30 over four weeks, then only the occurrence on the exception date is classified as after-hours; other occurrences follow the flex schedule. - Exceptions override team defaults and user base windows for their time span only and do not affect other users. - Removing an exception updates future classifications starting immediately and does not retroactively change past classifications. - Overlapping exceptions are resolved by the most specific rule (shorter duration, more recent effective date).
Penalty Score with Core Hours and Commute Buffers
- Penalty score range is 0–100; score is 0 when 100% of the meeting is within the user’s work window and >0 when any portion lies in buffers or outside the window. - For two meetings of equal duration, the penalty increases monotonically with the number of minutes outside the work window (including commute buffer minutes as outside). - Minutes inside the work window but outside preferred core hours contribute a smaller penalty than minutes outside the work window; given otherwise identical meetings, the score where minutes are outside core-only is strictly less than where the same minutes are outside the window. - For a 60-minute meeting with window 10:00–18:00 and 15-minute buffers, 09:45–10:45 yields a lower penalty than 09:30–10:30, which yields a lower penalty than 09:00–10:00. - Scores are deterministic: repeated calls with identical inputs return the same numeric value; score calculation is independent of consumer (Fatigue Radar vs. scheduling) and of request concurrency.
On-Call Rotations Modify Classification
- Given a weekly on-call rotation adding an on-call window 22:00–06:00 local for a user from 2025-10-14 to 2025-10-21, then meetings within 22:00–06:00 during that period are not after-hours for that user; after the rotation ends, identical meeting times are after-hours again. - On-call windows are combined with the user’s regular work window (union) for classification; commute buffers apply at the edges of on-call windows as they do for regular windows. - During on-call, meetings within the on-call window have zero after-hours penalty; meetings adjacent to the on-call buffer accrue appropriate buffer penalties. - Overlapping multiple rotations resolve by union without double-counting minutes in penalty computations. - On-call effective dates/times are respected per user and per region; users not on-call are unaffected by another user’s rotation.
Reusable Classification Service and Cross-Consumer Consistency
- The service exposes POST /v1/after-hours/classify that returns {after_hours_flag, penalty_score (0–100), window_segments, metadata}; given identical inputs, Fatigue Radar, scheduling suggestions, and fairness rotation receive identical outputs. - Contract tests validate that for a reference corpus of 200 scenarios, all three consumers produce byte-for-byte identical classification payloads (excluding trace IDs) and pass. - Performance SLO: p95 latency ≤ 200 ms and p99 ≤ 500 ms at 100 RPS with 500 concurrent users; availability ≥ 99.9% rolling 30 days. - Configuration changes propagate to the service within 60 seconds; requests made >60 seconds after a change reflect the new rules; requests made <60 seconds may reflect prior rules (documented eventual consistency). - Requests without a valid IANA timezone or with an unknown region return HTTP 400 with a machine-readable error code; idempotency and deterministic outputs are guaranteed for retried requests with the same idempotency key.
Next‑Sprint After‑Hours Forecasting Engine
"As a director of product, I want a forecast of after-hours hotspots for the upcoming sprint so that I can intervene before burnout and no-shows occur."
Description

Predict next-sprint after-hours risk by person, team, and region using historical cadence of recurring meetings, fairness rotation schedules, seasonality, daylight saving shifts, PTO forecasts, and pending reschedules. Output expected counts and minutes after-hours, risk scores, confidence intervals, and burnout cluster detection. Support configurable sprint windows and horizon (1–3 sprints). Provide an API that the heatmap queries, with SLAs suitable for interactive use. Calibrate and monitor model accuracy with backtests and drift alerts. Integrates with the policy engine for classification and with TimeTether’s scheduler to evaluate alternative scenarios.

Acceptance Criteria
Forecast Outputs by Person, Team, and Region
Given a valid tenant and data scope (person, team, region) and a next-sprint window When the forecasting API is called for that scope Then each returned entity includes: entity_id, scope, sprint_window (start,end ISO-8601), expected_after_hours_count (integer ≥ 0), expected_after_hours_minutes (integer ≥ 0), risk_score (0–100), confidence_interval_minutes {lower, upper}, confidence_interval_count {lower, upper}, burnout_cluster {flag:boolean, id:nullable} And all numeric values are finite; lower ≤ point estimate ≤ upper for both intervals And the response includes a generated_at timestamp and version metadata And results are returned for 100% of in-scope entities or per-entity errors are listed with standardized error codes
Configurable Forecast Horizon and Calendar Effects
Given a request with horizon parameter in [1,2,3] When horizon is valid Then the API returns a forecast slice per sprint with correct sprint_window boundaries And when horizon is outside [1,3] Then the API returns HTTP 400 with code "invalid_horizon" And when the next sprint crosses a daylight saving transition for any entity’s timezone Then after-hours classification uses local-time rules with offsets applied correctly (aggregated minutes error across DST week ≤ 5 minutes per entity compared to expected) And when PTO is present for a day Then predicted after-hours minutes for that person on that day = 0 And when a series has a pending reschedule for next sprint Then the forecast reflects the updated slots, not the historical time
Interactive API Performance and Availability
Given typical heatmap queries (team ≤ 200 people, horizon=1) When issuing requests under steady load (50 RPS per tenant) Then p95 latency ≤ 700 ms and p99 latency ≤ 2,000 ms and error rate (5xx) < 0.1% And for org-wide queries (≤ 2,000 people, horizon=1) Then p95 latency ≤ 1,200 ms and p99 latency ≤ 2,500 ms And monthly availability (successful responses/total) ≥ 99.9% And maximum response payload size ≤ 1 MB per 500 entities (responses are paginated when larger) And requests time out at 5,000 ms with HTTP 504 and retry-after header
Backtest Calibration and Accuracy
Given rolling backtests over the last 8 completed sprints When comparing forecasts to actuals Then MAPE for after-hours minutes ≤ 15% and MAPE for after-hours counts ≤ 20% at the person level And 90% confidence intervals achieve empirical coverage between 88% and 92% And risk_score discriminates after-hours occurrence with ROC AUC ≥ 0.75 And mean percentage error (bias) is between -5% and +5% across segments (person, team, region, timezone) And backtest metrics are computed nightly, stored, and visualized; threshold breaches emit alerts
Data and Performance Drift Alerts
Given weekly drift evaluation jobs When feature distribution drift PSI > 0.2 for any critical feature (timezone, meeting hour, series id, PTO flags) Then a Data Drift alert is created within 15 minutes with impacted segments And when backtest accuracy falls outside thresholds for 2 consecutive sprints Then a Performance Drift alert is created and retraining is recommended And all drift alerts are posted to on-call channel and ticketed as SEV-3 incidents And drift suppressions can be configured with expiry and are audited
Policy Engine Classification Integration
Given current work-window policies per person/team/region from the policy engine When classifying meetings as after-hours for forecasting Then 100% of sampled classifications match the policy engine’s decision service for the same inputs And policy changes propagate to new forecast requests within 5 minutes And if the policy engine is unavailable Then the API returns HTTP 503 with code "policy_unavailable" and does not serve stale classifications
Scheduler Scenario Evaluation and Deltas
Given a baseline forecast and an alternative schedule scenario payload (proposed changes to recurring series for the next sprint) When POSTing to the scenario comparison endpoint Then the response includes per-entity and aggregate deltas: Δafter_hours_minutes, Δafter_hours_count, Δrisk_score, cluster membership changes, and 90% CI deltas And for team-level scenarios (≤ 100 series, ≤ 200 people) p95 latency ≤ 1,200 ms And identical scenario payloads within 10 minutes return cached, identical results (idempotent hash) And scenarios are not persisted unless save=true is provided
Interactive Heatmap Visualization & Drilldowns
"As a team lead, I want an interactive heatmap that shows who is at risk and why so that I can quickly spot and prioritize fixes."
Description

Deliver an interactive heatmap that surfaces predicted next-sprint after-hours hotspots by person, team, and region with color-coded risk. Provide filters (org, team, role, region, time zone, meeting series), sorting, and time window toggles. Enable drilldowns from any cell to reveal underlying meetings, days, and specific time windows contributing to the score, with tooltips and sparkline trends. Ensure responsive performance for large organizations, accessibility compliance, and support for shareable, access-controlled views. Integrates with the forecasting API for data, the explanations layer for drivers, and the core dashboard for navigation.

Acceptance Criteria
Heatmap Rendering with Color-Coded Risk
- Given an authorized user with Fatigue Radar access and valid forecasting API connectivity, when the heatmap view is opened, then the heatmap renders cells for the selected dimension (person, team, region) with risk colors and a visible legend using risk bands provided by the API within 2 seconds on a standard desktop. - Given risk values in the API response, when the heatmap is rendered, then each cell’s numeric risk value and tooltip value match the API value to within ±0.1%. - Given cells with no data, when the heatmap is rendered, then those cells are shown with a distinct “no data” pattern and aria-label indicating no data, and they are excluded from risk-based sorting and aggregation. - Given a successful data fetch, when the heatmap is rendered, then a Last Updated timestamp in the user’s local time zone is displayed and matches the API’s asOf value.
Filter, Sort, and Time Window Controls
- Given filters for org, team, role, region, time zone, and meeting series, when a user applies any combination of multi-select filters, then the heatmap updates within 500 ms median and the result counts reflect the filtered population. - Given a sort control, when the user sorts by risk ascending/descending or alphabetically by label, then the ordering updates correctly and remains stable for equal values. - Given time window toggles (e.g., work hours, extended hours, custom), when a user changes a toggle, then the underlying query parameters update and the heatmap recomputes risk using the selected windows. - Given a set of active filters and toggles, when the user navigates away and returns within the same session, then the previous state is restored; when the user copies a deep link, then that state is encoded in the URL.
Cell Drilldown to Drivers and Meetings
- Given any heatmap cell, when the user clicks or presses Enter/Space on it, then a drilldown panel opens showing contributing meetings, days, and specific time windows along with their local times for relevant participants. - Given the explanations API, when the drilldown loads, then the top drivers (meeting series, days, time windows) are listed with their contribution percentages summing to 100% ±1%. - Given long lists of contributing meetings, when the drilldown opens, then the first 25 items are shown with lazy load for more, and search/filter within the panel narrows results in under 300 ms median. - Given a meeting item in the drilldown, when the user selects “Open details,” then they are navigated to the meeting detail page in the core dashboard in the same tab, with a back control returning to the heatmap context.
Tooltip Details and Sparkline Trends
- Given pointer hover or keyboard focus on a heatmap cell, when the tooltip appears, then it includes current risk value, person/team/region label, and a sparkline of the past 6 sprints with up/down trend arrow matching computed trend. - Given touch devices, when a user taps a cell, then the tooltip opens and can be dismissed with a second tap or outside tap, without obstructing adjacent cells. - Given accessibility needs, when a cell receives keyboard focus, then tooltip content is announced via aria-describedby without trapping focus, and the tooltip is dismissible with Esc. - Given the heatmap data and historical series, when the sparkline renders, then the plotted points and min/max align with historical values from the API to within ±1 unit.
Performance and Scalability for Large Organizations
- Given a dataset of 50,000 people across 200 teams and 8 regions, when the heatmap loads, then initial contentful render occurs within 3 seconds at p95 on modern desktop and 5 seconds at p95 on mid-tier laptop. - Given scrolling or panning the heatmap, when the user interacts, then frame rate remains ≥ 50 FPS median with virtualization enabled and memory usage remains below 500 MB in the browser. - Given filter or sort changes, when applied, then the heatmap updates within 800 ms at p95 with request batching and caching enabled; background API calls are limited to ≤ 2 concurrent per service. - Given API latency spikes or failures, when timeouts occur (>3 s), then requests are retried with exponential backoff up to 2 times and the UI shows a non-blocking toast plus inline retry.
Accessibility Compliance (WCAG 2.2 AA)
- Given the heatmap color palette, when evaluated, then all foreground/background pairs meet contrast ratio ≥ 4.5:1 and risk categories are distinguishable without color via patterns or icons. - Given keyboard-only navigation, when tabbing through the view, then every interactive control and cell is reachable in a logical order, has a visible focus indicator, and supports activation with Enter/Space. - Given screen reader use, when navigating cells, then each cell announces label, role gridcell, coordinates, risk value, risk band, and selection state via ARIA; tooltips and drilldowns are accessible dialogs with proper labelling. - Given motion sensitivity, when animations play (e.g., sparkline hover, transitions), then they respect prefers-reduced-motion and provide non-animated alternatives.
Shareable, Access-Controlled Views and Deep Links
- Given a configured view, when the user copies a share link, then the URL encodes filters, sorts, and time window toggles and opens to the same state when visited. - Given access controls, when an unauthorized user opens a share link, then a 403 page is shown without leaking metadata; when an authorized user opens it, then the view loads successfully. - Given revocation, when the owner revokes a link, then subsequent visits return 410 and the link is removed from the owner’s list within 60 seconds. - Given audit requirements, when a share link is created or accessed, then an audit log entry is recorded with user, timestamp, and view hash; logs are retrievable via admin console.
Driver Attribution & Explanation Layer
"As a scrum master, I want to see which meetings and time windows are causing the risk so that I know exactly what to change."
Description

Compute and display the primary drivers of each hotspot, attributing risk to specific meeting series, days, and time windows with contribution percentages. Provide plain-language explanations and inline ‘what-if’ controls to simulate shifts (e.g., move series by 30 minutes, rotate start times) with immediate impact estimates. Persist explanation artifacts to support auditability and stable references in shared links. Integrates tightly with the heatmap UI and the scheduler to convert insights into actionable changes.

Acceptance Criteria
Driver Attribution Panel for Hotspot Cell
Given a next-sprint after-hours hotspot is selected in the heatmap (person, team, or region context) with active filters and time range When the user opens the Drivers tab Then the panel lists the top 5 drivers with type tags (Series, Day, Time Window) And each driver displays a contribution percentage rounded to 1 decimal place; the sum equals 100% ±0.5% after rounding And at least one driver from each available category (Series, Day, Time Window) is included or an explicit “No drivers in category” placeholder is shown And each driver item deep-links to the underlying schedule instances (with count) respecting permissions And the panel loads within 1500 ms at the 95th percentile for hotspots with up to 500 series in scope
Attribution Method Fidelity and Reproducibility
Given identical filters, model version, and data snapshot When attribution is recomputed Then the driver list order and percentages are identical within ±0.1 percentage points When the underlying data or model version changes Then a new attribution version with a new immutable ID is produced and prior versions remain retrievable Then the attribution uses next-sprint forecast attendance and timezone-adjusted work windows consistent with the Fatigue Radar model And attribution excludes cancelled meetings and weights tentative invites at 50% per model rules And unit tests cover daylight savings transitions, split shifts, and rotating holidays
Plain-Language Explanations Rendering
Given drivers are computed When rendering the explanation Then a summary in plain language (Flesch-Kincaid grade ≤ 9) names the top series/day/window and quantifies expected after-hours impact (minutes and occurrences) And timezone names and local times are displayed for affected participants; relative terms map to explicit local time ranges And tooltips reveal how percentages were calculated; no internal jargon or model IDs appear And localization renders dates/times in the user locale (en-US and en-GB at launch) and numerals in the user’s number format
Inline What-If Shift Controls
Given a driver series is selectable When the user applies a shift of ±15/30/60 minutes Then the UI immediately displays updated after-hours minutes and hotspot risk change for affected entities, computed within 500 ms at p95 And changes are non-destructive until Apply via Scheduler is confirmed And conflicts with constraints (e.g., organizer work window) are flagged inline and estimates reflect only feasible options And the user can reset to baseline in one click
Inline What-If Rotation Controls
Given a recurring series When Rotate start times is toggled with a weekly rotation across policy-defined time windows Then the estimate shows redistribution of after-hours minutes with equity score change and max individual after-hours decrease/increase And estimate accuracy versus a full recompute in a sandbox achieves MAPE ≤ 10% across 100 sampled scenarios And the proposed rotation can be handed off to the Scheduler with series ID, target windows, and rotation rule prefilled
Persistence and Stable Share Links
Given an explanation view is open When Copy shareable link is clicked Then an immutable artifact is persisted with driver list, percentages, explanation text, impact estimates, filters, entity context, time bounds, model version, and timestamp And the link remains resolvable for at least 180 days with access controls; unauthorized users see an access request screen without content leakage And the artifact renders exactly as saved regardless of subsequent data/model changes, with a banner showing Snapshot from {timestamp} (model {version}) And an audit log entry is recorded with user, action, and artifact ID
Heatmap and Scheduler Integration
Given the user is viewing a hotspot explanation When Open in Scheduler is clicked Then the Scheduler opens with a draft proposal reflecting the chosen what-if adjustment (shift or rotation), impacted attendees, target windows, and a rationale summary And the user can send one-click invites from the draft; upon send, the heatmap marks the hotspot as Change Proposed and links back to the scheduler item And if the proposal is discarded, no production state is mutated and hotspot status remains unchanged And end-to-end telemetry records conversion from insight to action with time-to-open p95 ≤ 2 seconds
Proactive Alerts & Fix Recommendations
"As a program manager, I want timely alerts with actionable recommendations so that I can reduce after-hours load before the sprint starts."
Description

Trigger proactive alerts when predicted risk exceeds configurable thresholds for a person, team, or region. Deliver notifications via Slack and email with context (top drivers, affected windows) and one-click recommendations that open TimeTether to propose fair alternative slots, staggered rotations, or series adjustments. Support bulk actions, snooze/acknowledge workflows, and post-action impact tracking to quantify reductions in after-hours load. Close the loop by feeding outcomes back into the model for continuous improvement.

Acceptance Criteria
Threshold Configuration by Scope
Given org-, region-, team-, and person-level threshold settings with inheritance When an admin sets thresholds in % of predicted after-hours minutes (0–100) and clicks Save Then values are validated, persisted, and audit-logged (actor, scope, old→new, timestamp) And effective threshold resolves to the most specific scope for each entity And new thresholds take effect on the next prediction run (<= 15 minutes) without service restart
Proactive Alert on Threshold Breach
Given next-sprint predictions are computed for all entities When an entity’s predicted after-hours minutes exceeds its effective threshold Then a severity is assigned (Low: 0–10% over threshold, Medium: 10–25% over, High: >25% over) And a single alert is created per entity per driver cluster within a 24h deduplication window And the alert includes risk score, delta vs threshold, top 3 drivers, and affected days/time windows
Multi-Channel Notifications with Context
Given an alert is created When notifications are dispatched Then a Slack message is delivered to the configured recipient(s) and an email to the notification list within 60 seconds And both include entity, severity, risk score, delta vs threshold, top 3 drivers, affected windows, and one-click recommendation links And delivery status is recorded; Slack failures retry 3 times with exponential backoff and fall back to email-only
One-Click Fix Recommendations
Given an alert with identified drivers When a user clicks a recommendation link (alternative slots, staggered rotation, or series adjustment) Then TimeTether opens to a pre-filtered view for the affected entity and series with pre-populated fair options And the user can create and send a proposal in ≤ 3 clicks And the action is audit-logged and linked to the original alert
Bulk Action Execution
Given a user selects 2–100 compatible alerts When they apply a bulk recommendation type and confirm Then a pre-execution impact summary (projected after-hours reduction, impacted attendees, time windows) is shown And proposals are created for all items; partial failures are reported per item without aborting the batch And a single bulk audit record links all items with success/failure counts
Snooze and Acknowledge Workflow
Given an active alert When a user snoozes it for N days (1–28) Then the alert is suppressed from notifications and lists until the snooze expires When a user acknowledges an alert Then the alert is marked Acknowledged and will not re-notify for the current sprint And both actions are reversible and audit-logged; resumed alerts retain history
Post-Action Impact Tracking and Model Feedback
Given proposals created from an alert are accepted or rejected When the next prediction run completes after the effective changes Then the system computes and displays projected vs actual change in after-hours minutes for the impacted entity and series And impact is attributed to the action in reporting and included in weekly summaries And the outcome is written back as labeled data to the model store within 24 hours for continuous improvement
Role‑Based Access & Privacy Controls
"As a privacy-conscious admin, I want strict access controls and anonymized views so that sensitive scheduling patterns are protected."
Description

Enforce fine-grained RBAC for Fatigue Radar with scope-based access (org-wide, team, region) and anonymization options for executive rollups. Minimize PII exposure by showing only necessary fields, support consent where required, and honor data retention policies. Provide audit logs for views and exports, and ensure compliance with regional privacy regulations (e.g., GDPR). Apply the same controls to APIs, heatmap links, and alerts to maintain consistent data protection across channels.

Acceptance Criteria
UI Access Scope Enforcement (Org/Team/Region)
Given a user with role "Team Viewer" scoped to Team A, When they open Fatigue Radar, Then they can see only Team A data and cannot navigate to other teams, regions, or org-wide views. Given a user with role "Regional Analyst" scoped to Region EMEA, When they filter or drill down, Then results are limited to EMEA members and time windows; cross-region data is excluded from queries and UI. Given a user with no assigned scope, When they attempt to access Fatigue Radar, Then access is denied with 403 and no data is loaded. Given a user with multiple scopes, When they toggle scope selectors, Then the UI and queries reflect only the selected authorized scope(s) and never aggregate beyond their permissions. Given a deep link to an unauthorized scope, When followed, Then the system returns 403 and redacts any metadata for that scope.
Executive Rollup Anonymization Thresholds
Given anonymization is enabled with threshold k=5 for executive rollups, When a segment has fewer than 5 individuals, Then individual identifiers are hidden and only aggregated metrics are shown. Given anonymization is enabled, When viewing executive rollups, Then names, emails, and calendars are replaced with masked labels and no drill-through to individual records is available. Given an export is generated from a rollup view, When anonymization is enabled, Then the exported file contains only aggregated fields and no PII. Given filters are applied that reduce group size below k, When results render, Then the UI displays "Insufficient group size" and withholds small-n details to prevent re-identification. Given anonymization settings are changed, When saved, Then all active sessions apply the new policy within 60 seconds.
PII Minimization & Field-Level Masking
Given the default PII policy, When viewing Fatigue Radar at any scope, Then only the minimal necessary fields are displayed (e.g., alias/first name, team, region, timezone offset, role) and emails, phone numbers, exact location, and calendar URLs are not shown. Given a user without "PII Viewer" permission, When they attempt to reveal PII fields, Then controls are disabled and the API omits those fields from payloads. Given an admin toggles "Show Emails" for a role, When saved, Then only that role sees email addresses and all other roles continue to see masked values. Given drill-down to a series driver is available, When selected, Then the UI shows series identifiers and schedule windows without exposing personal contact details. Given alerts are configured, When an alert is delivered, Then it respects the PII policy and excludes non-permitted fields.
Consent-Gated Processing & Display
Given a user is mapped to a region requiring consent, When consent has not been granted, Then their data is excluded from Fatigue Radar computations and UI. Given a user revokes consent, When revocation is recorded, Then their historical data is removed from analytical stores within 72 hours and excluded from future processing. Given a consent record exists with timestamp and lawful basis, When an admin audits a profile, Then the consent status, source, and history are viewable without exposing unnecessary PII. Given consent is required, When a data subject grants consent, Then inclusion begins on the next processing cycle and is logged. Given no consent is required for a region, When processing occurs, Then the system records the applicable legal basis and does not prompt for consent.
Retention Windows and Right-to-Erasure
Given an organization sets a retention period of 12 months for Fatigue Radar data, When data exceeds 12 months of age, Then it is purged from primary stores, analytics indices, caches, and backups scheduled for deletion per policy. Given a data subject deletion request is received, When verified, Then all PII and related Fatigue Radar records are deleted or irreversibly anonymized within 30 days and this action is logged. Given exports are generated, When retention rules deprecate included records before download, Then the export is re-generated to exclude expired data or the download is invalidated. Given audit logs must be retained, When a deletion request is processed, Then audit log entries are preserved without PII (e.g., subject identifiers hashed) while maintaining traceability. Given retention settings are updated, When saved, Then a backfill job evaluates existing data and schedules required deletions within 24 hours.
Auditable Views, Exports, and API Access
Given any user views a heatmap, When the view is rendered, Then an audit log entry records user ID, role, scopes, anonymization state, timestamp, dataset version, and client IP. Given an export is initiated, When the file is generated or fails, Then an audit log entry records export name, fields included, row counts, requester, destination, and success/failure. Given an API call to Fatigue Radar endpoints occurs, When processed, Then an audit log entry records endpoint, parameters, row counts, caller identity, and result code, without logging PII payload bodies. Given an admin searches audit logs, When filters are applied, Then results can be filtered by time range, actor, action type (view/export/api), scope, and outcome. Given audit logs are protected, When a non-admin attempts access, Then access is denied and the attempt itself is logged.
Unified Controls Across UI, APIs, Links, and Alerts
Given a shareable heatmap link is created, When an unauthorized user follows it, Then access is denied and no data is leaked in metadata; links include short-lived tokens that expire within 24 hours by default. Given a user with limited scope opens a shareable link, When the link scope exceeds their permissions, Then the response is constrained to the user's scope or denied per policy without exposing out-of-scope aggregates. Given an API token is generated, When used, Then responses include only data within the token’s scopes and respect anonymization and PII policies. Given alerts are sent via email or chat, When generated, Then content is auto-redacted per the recipient’s role and scope; deep links inherit the same controls. Given a channel is temporarily unavailable, When a request is retried, Then no widening of permissions occurs and caching does not bypass RBAC/anonymization.

Smart Shift

Actionable, ranked slot shifts that dissolve predicted clusters with minimal disruption. Accept a one‑click swap or slight time move; TimeTether updates invites, notifies attendees via Slack/Teams, and preserves cadence and fairness.

Requirements

Cluster Prediction Engine
"As a distributed team lead, I want the system to proactively detect upcoming meeting clusters so that I can reduce after-hours burden before it happens."
Description

Analyze upcoming recurring meetings across calendars to detect and predict high-density time clusters that risk after-hours load. Use historical attendance, timezone overlap, declared work windows, quiet hours, and recurrence patterns to surface hotspots and candidate meetings for shifting. Provide a service API that emits cluster hotspots with confidence scores, constraint summaries, and recommended target windows. Runs nightly and on-demand upon schedule changes, with multi-tenant isolation and data minimization.

Acceptance Criteria
Nightly Batch Detection Job
Given it is 02:00 UTC daily and tenant T has <= 10,000 recurring meetings, When the engine runs, Then it completes within 15 minutes and emits hotspots for all detected clusters with timestamps and counts. Given an execution failure occurs, When the retry policy triggers, Then the job retries up to 3 times with exponential backoff and emits a failure metric and alert tagged by tenant. Given a tenant is disabled, When the job runs, Then the tenant is skipped and a skip reason is recorded.
On-Demand Trigger on Schedule Change
Given a meeting in tenant T is created, updated, or canceled, When the change event is received, Then an on-demand analysis for the affected window starts within 60 seconds and completes within 2 minutes for <= 500 affected meetings. Given multiple changes arrive within 60 seconds for the same series, When events are processed, Then only one analysis runs (coalesced) and subsequent duplicates are dropped. Given > 50 triggers per minute occur for tenant T, When backpressure applies, Then triggers are queued and processed oldest-first without data loss.
API Response Contract and Tenant Isolation
Given an authenticated request with tenant_id=T, When GET /v1/cluster-hotspots?window=... is called, Then the response is HTTP 200 and includes only data for T and each hotspot contains: hotspot_id, time_window, meetings_count, after_hours_risk_score [0..1], confidence [0..1], constraints_summary, recommended_target_windows[]. Given an unauthenticated or cross-tenant token is used, When the endpoint is called, Then HTTP 401 or 403 is returned and no tenant data is leaked. Given > 500 hotspots match, When pagination parameters are provided, Then results are paginated with stable cursors and consistent ordering.
Data Minimization and Privacy Controls
Given hotspot generation executes, When calendars are read, Then only meeting_id, start/end, recurrence_id, hashed_attendee_id, attendee_timezone, attendance_rate (0..1), declared_work_window, quiet_hours, and tenant_id are stored/transmitted; titles, descriptions, and raw emails are not persisted. Given logs and metrics are emitted, When records are written, Then they exclude PII and include only tenant_id, counts, durations, and anonymized identifiers. Given a tenant requests data purge, When the purge job runs, Then all cluster artifacts for that tenant are deleted within 24 hours and the deletion is recorded in an audit log.
Prediction Quality Using Historical Signals
Given a labeled validation dataset, When the engine predicts high-density clusters, Then precision >= 0.80 and recall >= 0.75 for after-hours risk hotspots at confidence >= 0.60. Given declared work windows and quiet hours conflict with meeting times, When risk is computed, Then the after-hours risk score increases monotonically with the fraction of impacted attendees. Given participants have attendance_rate < 0.50 historically, When risk contribution is calculated, Then their weight is reduced by at least 50%.
Recommendations and Constraint Summaries
Given a hotspot is emitted, When recommendations are produced, Then 1-5 recommended_target_windows are returned, each including start, end, expected_disruption metric, fairness_impact_delta, and feasibility_score [0..1]. Given a fairness policy requiring rotation applies, When multiple windows are equally feasible, Then rankings minimize after-hours burden variance across attendees. Given a constraints_summary is returned, When inspected, Then it lists binding constraints (timezones, declared windows, quiet hours, recurrence patterns) with counts of affected attendees and identifies the top 3 constraints contributing >80% of infeasibility.
Monitoring, SLOs, and Alerting
Given normal operation, When viewing service metrics, Then dashboards show per-tenant run counts, duration percentiles (p50/p95/p99), hotspots generated, and error rates aligned to published SLOs. Given a code change is canaried, When automated analysis runs, Then no regression >10% in p95 runtime or in precision/recall metrics is detected before full rollout proceeds. Given a critical error occurs, When detected, Then an alert is fired within 2 minutes including tenant impact and run identifiers.
Ranked Shift Recommendations
"As a scheduler, I want a clear, ranked set of low-disruption shift options so that I can fix conflicts quickly and confidently."
Description

Given detected hotspots, generate a ranked list of actionable shift options (swap meetings, slide by ±15/30/45 minutes, or move within work windows) that dissolve clusters with minimal disruption. Optimize ranking for fairness score, attendee impact, buffer preservation, timezone equity, organizer preferences, and recurrence integrity. Return executable proposals with affected series/instances, proposed times in local time, disruption cost, fairness deltas, and constraint validations.

Acceptance Criteria
Ranked Options Generated for Hotspots
- Given a detected hotspot with ≥3 meetings overlapping within a 120-minute window and at least one movable meeting, When shift recommendations are generated, Then the system returns between 3 and 10 ranked options, or 0 with reason when no viable moves exist. - Given any returned list of options, When sorted by the system, Then the options are ordered by composite_score descending and the top option reduces total overlap minutes within the hotspot by ≥30%. - Given a request under normal load (≤200 events in the day scope), When processed, Then the P95 generation latency is ≤ 2.0 seconds.
Allowed Shift Variants Coverage
- Given a hotspot with at least two movable meetings, When generating options, Then the list includes at least one swap option and at least one slide option of ±15, ±30, or ±45 minutes. - Given any slide option, When validating, Then the proposed time falls within all attendees' configured work windows and preserves a minimum buffer of ≥10 minutes before and after for all affected attendees. - Given any option, When validating against availability, Then no attendee is scheduled during Do Not Disturb or PTO blocks.
Multi-Factor Ranking and Scoring
- Given two or more viable options, When scored, Then each option includes the fields: composite_score (0–100), fairness_delta (-10 to 10), attendee_impact (0–100), buffer_preservation (0–100), timezone_equity_delta (-10 to 10), organizer_pref_alignment (0–100), recurrence_integrity (0–100), and disruption_cost (0–100). - Given ranking weights are configured and sum to 1.0 with fairness weight being the largest, When computing composite_score, Then the top-ranked option has the highest composite_score; ties are broken by lower disruption_cost, then by earliest proposed_start. - Given request tracing is enabled, When the score is computed, Then the applied weights and factor scores are written to the audit log for the request.
Executable Proposal Payload Completeness
- Given an option is returned, When inspected, Then it contains: proposal_id, affected_series_ids, affected_instance_ids, proposed_start/end in ISO 8601 UTC, per-attendee local time with IANA timezone, execution_endpoint, one_click_token, and expires_at. - Given an option is returned, When inspected, Then it contains: disruption_cost (0–100), fairness_delta_team (-10 to 10), fairness_delta_per_attendee [{attendee_id, delta}], constraint_validations [{constraint, status: pass|fail, message}], and recurrence_change_summary. - Given the proposal_id is executed more than once, When duplicate execution occurs, Then only the first execution succeeds and subsequent attempts return a 409 Conflict.
Constraint Validation and Rejection Reasons
- Given any candidate option violates a hard constraint (outside work hours, min buffer, max daily meetings, DND/PTO), When filtering options, Then it is excluded from the returned list. - Given no viable options remain after filtering, When responding, Then the system returns an empty list along with a "reason" object that lists the top 3 blocking constraints and their frequencies. - Given an option impacts soft constraints (e.g., organizer preference), When included, Then the option contains a warning with quantified impact in the constraint_validations field.
Recurrence Cadence and Fairness Preservation
- Given a recurring series is affected, When proposing slides or swaps, Then the series RRULE is preserved or a consistent series-level update is proposed; per-instance exceptions do not exceed 10% of future instances. - Given a fairness rotation exists, When evaluating any option, Then the team fairness score does not decrease by more than 1 point (0–100 scale) and timezone equity does not degrade; the top-ranked option improves at least one of these by ≥1 point. - Given any option that alters a series, When scored, Then recurrence_integrity ≥ 80; if only a single instance moves within allowed exception limits, Then recurrence_integrity = 100.
One-Click Shift Execution
"As an organizer, I want to apply a recommended shift with one click so that the calendar updates and invites are handled for me."
Description

Enable single-click application of a selected recommendation across Google Calendar and Microsoft 365. Update recurring series or targeted instances while preserving cadence, meeting links, organizer ownership, and privacy. Automatically regenerate and send updated invites, reconcile ICS details, and maintain buffers. Ensure transactional execution with rollback on partial failures and idempotent retries for reliability.

Acceptance Criteria
One-Click Shift on Single Instance (Google Calendar)
Given a selected Smart Shift recommendation targeting a single occurrence in a Google Calendar recurring meeting and the user clicks "Apply Shift" once When the shift is executed Then only the targeted occurrence’s start and end times are updated to the recommended slot And the series RRULE remains unchanged And pre/post buffer constraints are enforced with no overlap into adjacent events per organizer’s buffer settings And the meeting link remains unchanged And the organizer remains unchanged And the event privacy setting (e.g., Private) remains unchanged And the attendee list remains unchanged And attendees receive exactly one updated invite for that occurrence And the instance retains the same UID with a RECURRENCE-ID and an incremented SEQUENCE in the ICS And the UI displays a success confirmation within 5 seconds
One-Click Shift on Recurring Series (Microsoft 365)
Given a selected Smart Shift recommendation to shift an entire Microsoft 365 meeting series and the user clicks "Apply Shift" When the shift is executed Then all future occurrences (from the effective date) move to the new time while preserving cadence (same RRULE frequency, interval, BYDAY, COUNT/UNTIL) And existing exceptions (modified or canceled instances) are preserved And meeting link, organizer, privacy, and attendees are preserved And pre/post buffer constraints are validated across all shifted future occurrences And if any future occurrence violates buffer or resource constraints, the shift is aborted and no changes are committed And attendees receive a single updated series invite reflecting new times And ICS/REST payloads reflect incremented version/SEQUENCE and updated DTSTART/DTEND across the series
ICS Regeneration and Attendee Update Delivery
Given a shifted event (single instance or series) with mixed-provider attendees (Google and Microsoft) When invites are regenerated and sent Then ICS files use METHOD:REQUEST with correct UID, RECURRENCE-ID (for instances), and incremented SEQUENCE And DTSTART/DTEND include correct TZID values matching the event’s calendar time zone And custom fields, description, attachments, and conferencing details are preserved And each attendee receives exactly one updated invite and no duplicate notifications are sent And the system records delivery success/failure per attendee with a correlation ID
Preserve Meeting Links, Organizer Ownership, and Privacy
Given any Smart Shift execution on a meeting with an existing conferencing link and marked privacy When the shift completes Then the conferencing URL (e.g., Google Meet, Microsoft Teams, Zoom) remains unchanged And the event organizer remains the same and retains edit permissions And the event visibility/privacy setting remains unchanged for all calendars And no internal notes or non-attendee comments are exposed in external attendee ICS payloads
Transactional Execution with Rollback on Partial Failure
Given a Smart Shift execution that updates the calendar event and then sends updated invites When a downstream step fails (e.g., invite send failure, provider API timeout) Then all event changes are rolled back to the pre-shift state across providers And no attendee receives a partial or stale update And the system surfaces a clear error to the user with a correlation ID And an audit log entry captures the failure reason, step, and affected resources And any automatic retries occur with the same idempotency key and do not create duplicates
Idempotent Retry Prevents Duplicate Updates
Given the shift execution is retried with the same idempotency key due to a transient error or timeout When the original attempt completes and/or the retry proceeds Then exactly one calendar update is committed And attendees receive at most one updated invite each And the event version/SEQUENCE increments exactly once And the operation is recorded once in audit logs And subsequent identical retries return success with an "already applied" result and no side effects
Attendee Notifications via Slack/Teams
"As an attendee, I want clear chat notifications with quick actions so that I can respond and adjust without searching my inbox."
Description

Deliver concise, localized notifications to attendees via Slack and Microsoft Teams upon proposal and execution. Include before/after times in each attendee’s local timezone, inline RSVP actions, and add-to-calendar deep links. Support channel and DM routing, message threading, snooze/mute preferences, and graceful fallback to email on delivery failure. Track delivery, reads, and responses for auditability.

Acceptance Criteria
Localized Notification Content for Shift Proposals and Executions
Given a Smart Shift proposal or execution for a meeting attendee with timezone T and locale L When TimeTether sends a Slack or Microsoft Teams notification Then the message includes clearly labeled Before and After start/end times rendered in the attendee’s local timezone T with timezone abbreviation (e.g., PDT, CET) And dates and numbers are formatted according to locale L And any DST transition applicable to the scheduled dates is reflected in the displayed times And the message content fits within the target platform’s message limits without truncation or broken formatting And the language used matches the attendee’s notification language preference, defaulting to English if unspecified
Inline RSVP Actions and State Update
Given a Smart Shift notification delivered via Slack or Microsoft Teams When the attendee clicks an inline action (Accept, Tentative, Decline) Then the response is recorded in TimeTether within 5 seconds and associated to the correct meeting instance and attendee And the original message (or its thread) is updated to reflect the attendee’s latest response state And duplicate clicks within 60 minutes are idempotently handled, preserving the first recorded response unless explicitly changed by a different action And if the shift proposal has expired or been superseded, the attendee sees an ephemeral notice explaining the status and no change is recorded
Add-to-Calendar Deep Links per Provider
Given an attendee with a detected or configured calendar provider (Google, Microsoft 365/Outlook, or unknown) When a Smart Shift notification is sent Then the message includes an Add to Calendar deep link tailored to the attendee’s provider that pre-populates title, description, conferencing link, attendees (if available), and the new start/end times in the correct timezone And for unknown providers an ICS file/link is provided with equivalent details And links are unique per attendee and per meeting instance and are valid for at least 30 days And following the link creates or updates the event at the shifted time without duplicating if an event with the same UID exists
DM/Channel Routing and Message Threading
Given a team routing configuration specifying DM, channel, or both for meeting shift notifications When TimeTether publishes a Smart Shift notification Then messages are delivered to the configured destinations (DM, channel, or both) respecting workspace permissions And if a prior notification thread exists for the same meeting in the destination, the new message is posted in that thread; otherwise a new thread is started And if channel posting fails due to permissions or missing membership, a DM is attempted; if DM is not possible, fallback rules apply without creating duplicates And each destination message includes a stable correlation ID to associate posts across DM and channel
Snooze and Mute Preference Enforcement
Given an attendee with active snooze (time-bound) or mute (indefinite) preferences for Smart Shift notifications When a notification would otherwise be sent Then no Slack or Teams message is delivered during the snooze window or while muted And upon snooze end, only the latest relevant state (most recent proposal or execution) is delivered as a single summary; intermediate notifications are suppressed And muted notifications are not delivered via alternate channels (no email fallback) because suppression is intentional, not a delivery failure And all suppressions are logged with reason and timestamps for audit
Graceful Fallback to Email on Delivery Failure
Given a Smart Shift notification targeted to Slack or Microsoft Teams When delivery fails due to a platform error (4xx/5xx), permission error, or timeout exceeding 15 seconds Then an email with equivalent localized content, RSVP actions, and add-to-calendar options is sent to the attendee within 60 seconds And if the original platform subsequently succeeds, duplicate user-facing notifications are prevented via idempotent correlation within a 24-hour window And the final delivered channel (Slack, Teams, Email) is recorded for the notification instance
Delivery, Read, and Response Audit Tracking
Rule: For each notification instance and attendee, TimeTether records delivery status (success/failure), platform, destination (DM/channel/email), timestamp, and message IDs/correlation IDs Rule: Read is recorded when the platform provides a read receipt; otherwise, read is inferred on any user interaction (button click, thread reply, reaction) and timestamped Rule: All RSVP actions are logged with actor, action, timestamp, and previous/new state, with idempotency keys to prevent duplicate records Rule: Audit records are immutable, queryable by meeting ID, attendee ID, and time range via admin console and API, and retained per data retention policy
Fairness & Cadence Guardrails
"As a manager, I want guardrails that enforce fairness and cadence so that no region or role is disproportionately burdened by shifts."
Description

Validate all recommendations against fairness policies (rotation balance, after-hours budgets, on-call exceptions), declared team working hours, and cadence rules (e.g., standups at consistent local times, biweekly reviews). Automatically block or de-rank options that worsen equity or break cadence, and display fairness deltas to the user. Provide admin-configurable thresholds and per-team overrides with audit logs of policy decisions.

Acceptance Criteria
Fairness validation: rotation balance and after‑hours budgets with on‑call exceptions
Given admin-defined thresholds for max rotation variance and weekly after-hours minutes per member And a candidate Smart Shift recommendation with identified attendees and on-call calendars When the fairness engine evaluates the candidate Then any member exceeding after-hours budget without an active on-call exception causes the candidate to be Blocked And any rotation variance beyond threshold causes the candidate to be Blocked And if metrics exceed only the warning band (<= configured tolerance), the candidate is De‑ranked instead of Blocked And the decision includes machine-readable reasons referencing which members and which limits were triggered
Working hours compliance across time zones
Given each attendee’s declared working window in their local timezone And a candidate meeting time produced by Smart Shift When the system converts the candidate time to each attendee’s local time Then the candidate is Blocked if any attendee’s start or end falls outside their declared window And if the team override allow_soft_violation is enabled, the candidate is De‑ranked instead of Blocked for those violations And the output includes the count of affected attendees and their local windows
Cadence preservation for recurring standups and biweekly reviews
Given a recurring series classified as Standup or Biweekly Review with a stored cadence baseline And an admin-configured cadence_drift_tolerance_minutes When Smart Shift proposes a new slot for the series Then for Standups the local start time per attendee must remain within the drift tolerance and day-of-week unchanged, otherwise Blocked And for Biweekly Reviews the proposal must preserve week parity and day-of-week, with start time within drift tolerance, otherwise Blocked And proposals within tolerance but not identical times are De‑ranked relative to exact-cadence matches
Automatic block vs de‑rank mechanics and ranked output
Given a set of candidate slots with computed fairness deltas and cadence compliance flags When generating the ranked recommendation list Then any candidate with a Block condition is excluded from the list And candidates with only De‑rank conditions are included after all neutral or improving candidates And the list is strictly ordered by (a) no violations, (b) net fairness improvement, (c) minimal fairness regression within tolerance, (d) minimal disruption minutes And each candidate includes a structured why payload detailing policy evaluations
Fairness delta disclosure to the user
Given a user inspects a candidate recommendation in the UI When the system renders fairness details Then it displays per-attendee deltas for rotation balance and after-hours minutes/week, and series-level cadence deviation in minutes And it highlights regressions vs improvements and explains any exceptions applied (e.g., on-call) And it shows the applicable policy thresholds and whether the candidate is Allowed, De‑ranked, or Blocked
Admin thresholds, team overrides, and policy audit logging
Given an admin with the appropriate role When they configure rotation variance, after-hours budget, and cadence drift thresholds globally and per-team overrides Then the saved configuration is versioned and applied to subsequent evaluations And every guardrail decision writes an immutable audit record capturing policy version, thresholds, overrides used, inputs (anonymized attendee IDs), outcome (Allow/De‑rank/Block), reasons, timestamp, and actor/service And admins can filter and export audit records by team, series, date range, and outcome
Guardrails gate one‑click swaps and invite updates
Given a user accepts a one‑click swap or minor time move from Smart Shift When guardrails re-evaluate the chosen candidate immediately prior to commit Then the system proceeds to update calendar invites and send notifications only if the candidate is Allowed (not Blocked) And if the candidate is De‑ranked but Allowed, proceed and include fairness deltas in the confirmation And if Blocked, prevent the change, surface the blocking reasons, and present up to three top Allowed alternatives
Impact Preview & Approval Window
"As an organizer, I want to preview the impact and optionally require approval so that I can avoid unintended consequences."
Description

Before execution, present an impact preview that lists affected meetings, attendees, fairness changes, buffers, and the notification plan. Allow optional approver gating with an expiration window and automatic rollback if approval is not granted or negative feedback exceeds a threshold. Persist an audit trail of previews, approvers, timestamps, and outcomes for compliance.

Acceptance Criteria
Impact Preview Completeness & Accuracy
Given a Smart Shift proposal exists When the impact preview is opened Then it lists all affected meeting instances with series/occurrence IDs and timestamps (UTC and attendee local time), impacted attendees per instance, fairness score deltas per attendee/team, buffer changes in minutes, and the notification plan (channel and timing) per recipient And When the preview payload is compared to the scheduler engine output for the same proposal Then counts and values match exactly for meetings, attendees, fairness deltas, and buffer changes And Then numerical values display with correct signs and units (minutes, percentages) and contain no null/NaN values
Approver Gating Configuration & Enforcement
Given approver gating is enabled for the workspace/team with an approver group configured When a Smart Shift is initiated Then the shift enters Pending Approval and no calendar updates or attendee notifications are sent And When a user in the approver group approves within the approval window Then the system executes the shift, updates invites, and sends notifications as listed in the preview And When a non-approver attempts to approve or reject Then the action is blocked with a permission error and no changes occur And When approver gating is disabled Then the initiator can execute immediately and no approval request is issued
Approval Window Expiration & Auto-Rollback
Given an approval window duration is configured and displayed in the preview When the window elapses without an approval Then the system cancels the shift and rolls back any tentative holds or draft invites And Then the initiator and impacted attendees are notified of expiration per the previewed notification plan And Then the audit trail records outcome "Expired" with created/expired timestamps, initiator, approver group, and no execution artifacts remain on calendars
Negative Feedback Threshold & Auto-Cancel
Given a negative feedback threshold T is configured and Slack/Teams/email feedback channels are connected When the count of unique negative responses within the approval window reaches or exceeds T Then the system cancels the shift and performs rollback with no calendar changes applied And Then the preview/UI shows the final feedback count and reason "Rejected by Feedback" And Then the audit trail records channel-level counts and the outcome "Rejected by Feedback" And Then duplicate negative responses from the same user are counted once
Notification Plan Consistency
Given the impact preview lists the notification plan with recipients, channels, and timing relative to approval or rollback When the shift is approved or canceled Then notifications are delivered to the listed recipients via the listed channels within the specified timing window And Then no recipient receives duplicate notifications for the same event And Then delivery outcomes (sent, delivered, failed) are logged per recipient, and failures are retried according to the system retry policy
Audit Trail Immutability & Export
Given a preview is generated When it is saved Then an immutable audit record is persisted containing preview ID, proposer, approver group, timestamps (created, approved/rejected/expired), outcome, decision actor(s), and a snapshot of the preview content And Then subsequent decisions or state changes create new entries without altering prior records And When an authorized user filters by date range, team, proposer, approver, or outcome Then the expected records are returned and can be exported as CSV and JSON matching the filtered set
Access Control & Visibility
Given role-based access controls are configured When an approver opens the preview Then they can view all preview details and see Approve/Reject controls And When an impacted attendee opens the preview link Then they can view only the portions relevant to meetings they attend and cannot approve/reject And When an unauthorized user attempts to view or act Then access is denied and the attempt is logged with user identifier and timestamp

Rotation Tuner

Proactively rebalances upcoming rotations to spread after‑hours load before problems appear. Preview the impact on each region’s burden, lock changes with policy guardrails, and keep recurring rituals humane without spreadsheet math.

Requirements

Fairness Impact Preview
"As a remote engineering leader, I want to preview how proposed rotation changes affect each region’s after‑hours load so that I can ensure adjustments are equitable before applying them."
Description

Provide an interactive dashboard that visualizes current versus proposed rotation impacts across regions, teams, and roles, including after‑hours minutes per person, percentage of meetings outside work windows, and fairness indices over time. Supports side‑by‑side comparison, aggregation by region, and drill‑downs to individual participants. Integrates with TimeTether’s schedule model to compute metrics from live calendar data and work windows, updating instantly as users tweak parameters in Rotation Tuner.

Acceptance Criteria
Side-by-Side Comparison View: Region-Level
- Given a tenant with at least two regions and a proposed rotation change, When the user opens Fairness Impact Preview with aggregation set to Region, Then two panels labeled "Current" and "Proposed" render with identical region sets and metric columns: After-hours minutes/person, % meetings outside work window, Fairness index. - Given identical filters are applied to both panels, When values are computed, Then Proposed metrics match the schedule model outputs for the same inputs (minutes within ±1 minute, percentages within ±0.1 percentage points, fairness index within ±0.01). - Given the user sorts by any metric, When toggling ascending/descending, Then both panels apply the same ordering and row alignment is preserved by region key. - Given there are more rows than fit on one page, When paginating, Then row alignment between Current and Proposed is maintained across pages. - Given totals are displayed, When computed, Then totals reflect participant-weighted averages for minutes/person and percentage, and the schedule model’s aggregation for fairness index, with units and rounding (minutes to nearest minute, percentages to 1 decimal).
Real-Time Metric Recalculation on Parameter Tweaks
- Given the user changes any Rotation Tuner parameter that affects scheduling (e.g., meeting start window, rotation length), When the change is applied, Then Proposed metrics and visualizations update within 2 seconds at p95 and 4 seconds at p99. - Given a recalculation is in progress, When viewing the Proposed panel, Then a non-blocking loading indicator is shown and interaction remains responsive. - Given multiple rapid parameter changes within 500 ms, When recomputing, Then calculations are debounced and only the latest change updates the UI (last-write-wins). - Given the schedule model API returns an error, When recalculation fails, Then the Proposed panel shows an error state with retry, preserves the last successful values clearly labeled "Stale", and no partial values are shown. - Given connectivity is restored after a failure, When retry is triggered, Then the Proposed panel clears the error, recomputes, and removes the "Stale" label upon success.
Aggregation Toggle: Region, Team, Role
- Given aggregation controls are available, When the user toggles between Region, Team, and Role, Then groups, labels, and counts update immediately and metrics are recomputed for the selected dimension. - Given participants lacking a value for the chosen dimension, When aggregating, Then they appear under an "Unassigned" group with correct metrics. - Given a group has zero scheduled meetings in the comparison window, When displayed, Then its after-hours minutes/person and % outside work window are shown as 0 and fairness index reflects the schedule model’s definition for empty/no-variance groups. - Given the user filters any dimension (e.g., exclude a team), When applied, Then both Current and Proposed panels reflect the same filtered population and totals recalculate accordingly. - Given the user changes time window (e.g., last 8 weeks), When applied, Then all aggregated metrics use the new window consistently across dimensions.
Drill-Down to Participant Metrics and Meeting Evidence
- Given a group row is visible, When the user clicks it, Then a drill-down view opens listing participants with columns: After-hours minutes, % outside work window, count of impacted meetings, and fairness contribution. - Given a participant is selected, When viewing details, Then a meeting list shows each meeting’s local start/end times, time zone, and flags indicating after-hours portions and outside-work classification. - Given participant-level metrics are shown, When summing the after-hours durations of listed meetings, Then the total equals the participant’s displayed after-hours minutes within ±1 minute rounding. - Given the user navigates back from participant details, When returning, Then focus and scroll position are preserved on the originating group row. - Given access control restrictions, When the user lacks permission to view a participant’s details, Then the participant row is obfuscated (e.g., anonymized) but included in group aggregates, and a permission notice is shown on click.
Fairness Indices Over Time Chart
- Given the time range selector is set (e.g., last 12 weeks), When the chart renders, Then it displays a weekly time series with two lines: Current and Proposed fairness index, aligned on the same x-axis. - Given the user hovers any point, When the tooltip appears, Then it shows the exact week boundary, Current value, Proposed value, and delta with units and precision to two decimals. - Given the time range or filters change, When applied, Then the chart updates within 2 seconds at p95 and maintains visual alignment with the tabled metrics for the same scope. - Given there are weeks with no meetings, When rendering, Then those weeks are either omitted or shown with a defined placeholder according to the schedule model’s semantics, and the legend includes a note indicating the behavior. - Given the user toggles a line off via the legend, When toggled, Then scales adjust so the remaining series is fully visible without distortion.
Live Calendar and Work Window Integration
- Given participants’ calendars and work windows are defined in TimeTether, When computing metrics, Then after-hours and outside-work calculations use each participant’s local time zone and respect DST transitions. - Given meetings overlap work window boundaries, When attributing after-hours time, Then only the minutes outside the defined window are counted as after-hours for each participant. - Given a meeting includes multiple participants across regions, When attributing metrics, Then per-participant minutes and outside-work flags are computed independently per their work windows and time zones. - Given the schedule model provides canonical metrics for the same inputs, When comparing, Then the dashboard values match the model’s outputs within the stated tolerances and are refreshed at most 5 minutes after underlying calendar changes. - Given calendar sync is delayed, When model freshness exceeds 5 minutes, Then a freshness indicator displays the last updated time and a prompt to refresh.
Predictive Rebalance Engine
"As a scheduling owner, I want the system to proactively generate fair rotation proposals so that I don’t need manual spreadsheets to find humane schedules."
Description

Implement a constraint‑aware optimization engine that forecasts upcoming meetings and rebalances rotations to minimize after‑hours burden while honoring timezones, role requirements, participation rules, and cadence. Produces ranked proposals with quantified impact deltas and confidence scores, factoring seasonality, holidays, and regional work‑week differences. Exposes tunable weights for fairness vs. stability and integrates with the Preview to simulate outcomes in real time.

Acceptance Criteria
Ranked Proposal Generation and Output Format
Given a baseline schedule for the next 8 weeks and default weights, When the engine runs, Then it returns between 3 and 10 proposals sorted by objective score descending. And each proposal includes: before/after after-hours minutes per region and per role; per-participant change count; fairness score (0–100), stability score (0–100), overall objective score; confidence score (0.00–1.00); unique proposal ID; ≤240-character rationale; list of changed meetings with old/new slots; constraint-satisfaction flag; generation timestamp. And the output is valid JSON matching the documented schema and is deterministic for identical inputs and random seed.
Hard Constraint Satisfaction and Feasibility Handling
Given hard constraints (timezones and local work windows, role coverage, mandatory participants, cadence), When generating proposals, Then no proposal violates a hard constraint or moves locked events. And if no feasible solution exists, Then the engine returns a no_feasible result with the top 3 conflicting constraints and zero proposals. And if soft constraints are enabled, Then proposals explicitly annotate any soft-constraint trade-offs and remain within configured soft-constraint bounds.
Fairness vs Stability Weight Tuning and Persistence
Given team-level weights fairness ∈ [0,1] and stability ∈ [0,1] in 0.05 increments, When weights are changed, Then the new values persist per team and are audit-logged with user, timestamp, and reason. And increasing fairness by ≥0.20 (holding others constant) decreases or maintains the variance of after-hours minutes across regions over the horizon. And increasing stability by ≥0.20 decreases or maintains the number of meeting moves in the top-ranked proposals. And default weights are fairness=0.60 and stability=0.40.
Seasonality, Holidays, and Regional Work-Week Awareness
Given regional calendars (public holidays), regional work-week definitions, and DST transitions, When forecasting and scoring after-hours, Then non-working days and regional weekends are excluded from work windows and after-hours is computed in local time respecting DST. And holiday calendars are applied per region and can be overridden per team. And proposals for the next 6 months incorporate these calendars with zero holiday conflicts in compliant proposals.
Real-time Preview Integration and Response Time
Given the Preview is open, When a user adjusts weights, pins/unpins a meeting, or toggles a guardrail, Then the top 3 proposals and impact deltas update to match an engine run with the same inputs. And end-to-end P95 update latency ≤1.5 s and P99 ≤2.5 s for teams up to 200 recurring meetings across up to 12 timezones. And server-side recomputation P95 ≤800 ms.
Policy Guardrails and Locked Items Compliance
Given policy guardrails (e.g., earliest local start, max after-hours per participant/month, region-specific no-meeting days) and locked meetings, When generating proposals, Then top-ranked proposals never violate guardrails or move locked items. And any proposal requiring an override is labeled requires_override, ranked below compliant proposals, and lists each violated rule. And role coverage rules (e.g., at least one Staff Engineer and one PM) are met in all compliant proposals.
Optimization Effectiveness and Impact Quantification
Given a baseline with ≥10 recurring meetings and at least one region with >10% after-hours burden, When the engine runs with default weights, Then the top compliant proposal reduces total predicted after-hours minutes by ≥20% and reduces the max regional after-hours share by ≥15%, or returns no_improvement with reasons. And each proposal displays quantified before/after deltas per region, per role, and per participant, plus fairness Gini change (ΔGini ≤ 0). And proposals with confidence <0.60 display a warning and cannot be auto-applied.
Policy Guardrails & Locking
"As an admin, I want to set guardrails and lock critical constraints so that rebalancing never violates company policies or moves protected rituals."
Description

Allow admins to define organization‑wide policies—such as maximum after‑hours minutes per user per month, protected no‑move meetings, regional quiet hours, and required meeting attendance rules—and enforce them during rebalancing. Provide locking controls to freeze specific participants, time slots, or rituals, with clear conflict explanations when proposals would violate policies. Logs all policy checks and outcomes for compliance visibility.

Acceptance Criteria
Enforce Max After-Hours Minutes Per User
Given an organization policy "max_after_hours_minutes_per_user_per_month" is set to 180 And each user has a defined work window in their local timezone When the Rotation Tuner generates a rebalance proposal for the current month Then no user’s projected after-hours total for the month exceeds 180 minutes And any candidate change that would cause an overage is excluded from the proposal set And the proposal summary lists each user’s projected after-hours minutes and remaining quota
Respect Regional Quiet Hours During Rebalance
Given regional quiet hours are configured per region (e.g., 20:00–07:00 local time) And quiet-hours overlap score is defined as the sum of minutes scheduled within quiet hours across all attendees When rebalancing recurring meetings involving users from those regions Then no meeting instance is scheduled within any attendee’s regional quiet hours And if no feasible slot exists that avoids all quiet hours, the system selects the candidate with the minimum quiet-hours overlap score and flags the residual impact And the selected candidate complies with all max caps and applied locks
Protect No-Move Meetings From Rescheduling
Given certain meetings are tagged as "No-Move" When a rebalance is executed Then the start time, duration, recurrence pattern, and attendee list of those meetings remain unchanged And proposals attempting to modify any of those attributes are rejected with reason code "protected_meeting" And the presence of No-Move meetings is treated as fixed constraints for other scheduling decisions
Enforce Required Attendance Rules
Given required attendance rules are configured for a ritual And the rules include All-of {Engineering Manager, Product Manager} and At-least-one-of {EMEA Rep, AMER Rep, APAC Rep} When generating a rebalance proposal Then every proposed instance satisfies all All-of rules and includes at least one member of each At-least-one-of group who is available and invited And proposals that cannot satisfy the rules are marked invalid with reasons referencing the missing roles/groups And the UI surfaces which rules were satisfied and which were unsatisfied per instance
Participant, Time Slot, and Ritual Locking Controls
Given an admin applies locks to specific participants, time slots, or rituals When rebalancing runs Then locked participants are not moved across time slots, locked time slots remain unchanged, and locked rituals are excluded from changes And lock states are visible in the UI with lock owner, scope, and timestamp And only users with the "Policy Admin" permission can remove or override a lock, requiring an explicit confirmation step And any override action is logged with before/after values
Conflict Explanations for Policy Violations
Given a candidate change violates one or more policies When the proposal is displayed Then a human-readable explanation is shown per violation including policy name, affected entity (user/meeting/region), measured value, limit/requirement, and time context And explanations provide links to the policy configuration And multiple violations are listed in priority order defined by the policy engine And the API returns machine-readable reason codes and metrics for each violation
Comprehensive Policy Check Logging and Auditability
Given any rebalance job or manual override triggers policy checks When checks execute Then the system writes a structured log entry per check with: timestamp (UTC), policy_id, policy_version, entity_ids, computed_metrics, outcome {pass|fail}, proposal_id, actor_id, and reason_codes And logs are immutable (append-only) and queryable by time range, policy, user, and proposal id within 5 seconds for up to 100k entries And logs are exportable as CSV and JSON within 15 seconds for up to 50k entries And access to logs requires the "view_policy_logs" permission; unauthorized requests are denied and recorded
What‑If Scenario Sandbox
"As a program manager, I want to run what‑if scenarios safely so that I can explore trade‑offs before deciding on a rotation change."
Description

Enable users to clone the current schedule into a sandbox to test alternative parameters—e.g., shifting work windows, adjusting fairness weights, or excluding regions—without affecting production calendars. Supports saving, naming, and sharing scenarios, with diff views that highlight changes and metric deltas compared to baseline. Scenarios can be promoted to proposals for approval and application.

Acceptance Criteria
Clone Current Schedule into Isolated Sandbox
Given a user with Scheduler or above permissions on a team calendar When they create a new What‑If Scenario from the current schedule Then the system clones the current schedule into a sandboxed snapshot labeled with the baseline timestamp And no calendar invites or notifications are sent And sandbox edits do not modify the production calendar And the clone operation completes within 8 seconds for schedules up to 200 recurring series or 5,000 instances
Adjust Work Windows Without Affecting Production
Given an existing sandbox scenario When the user modifies regional work windows and saves the changes in the scenario Then the scenario recomputes rotations using the new windows and updates the preview And the production calendar remains unchanged and receives no updates And invalid windows that violate organizational policy are blocked with a clear error And recomputation completes within 5 seconds for the dataset size defined in performance SLAs
Tune Fairness Weights and Preview Regional Burden
Given an existing sandbox scenario with baseline metrics When the user adjusts fairness weights (0.0–2.0 per rule guardrail) and releases the control Then the metrics panel updates within 500 ms to show per‑region after‑hours minutes, percentage after‑hours occurrences, and fairness score deltas vs baseline And the chosen weights are persisted in the scenario draft And out‑of‑range weights are rejected with an explanatory message and are not applied
Exclude Regions and Recompute Rotations
Given an existing sandbox scenario When the user excludes one or more regions and saves Then members in excluded regions are not assigned to any meetings in the scenario preview And the redistribution of after‑hours burden is reflected in the metrics with deltas vs baseline And the system requires explicit confirmation if any region’s after‑hours minutes increase by more than 15% And the exclusion is recorded in the scenario change history with user and timestamp
Save, Name, and Share What‑If Scenarios with Permissions
Given a completed set of scenario edits When the user saves the scenario with a name and shares it Then the name must be unique within the workspace; duplicates prompt for rename And a share link with Viewer or Editor roles is generated And Viewers can open and see diffs and metrics but cannot edit And Editors can modify and save; all changes are tracked with user and timestamp And sharing sends in‑app notifications only and never triggers calendar emails
Diff View: Highlight Schedule Changes and Metric Deltas vs Baseline
Given a scenario with changes relative to its baseline snapshot When the user opens the Diff view Then event changes are categorized and highlighted as Time Moved, Participant Changed, New, or Canceled And users can filter diffs by region, participant, and date range And metric deltas include after‑hours minutes per region, count of meetings outside preferred windows, and fairness score with absolute and percentage change And users can export the diff and metrics to CSV And unchanged events match the baseline 1:1 with zero false positives
Promote Scenario to Proposal and Apply with Policy Guardrails
Given a reviewed scenario When the user promotes it to a Proposal Then the scenario parameters are locked and a summary with diffs and metric deltas is generated And the proposal requires approval from at least one Product Lead and one Engineering Lead before apply And applying changes is atomic: either all calendar updates succeed or none are applied And policy guardrails are enforced (e.g., no meetings in hard no‑meeting windows; conflicts must be resolved before apply) And, upon successful apply, affected participants receive a single consolidated calendar update per series and an audit log entry is recorded
One‑Click Apply & Calendar Sync
"As a meeting organizer, I want to apply an approved rebalance in one click so that all calendars update reliably without manual edits."
Description

Provide a single action to apply an approved rotation proposal, updating recurring events, attendees, and time slots across integrated calendars. Ensures atomic updates with rollback on partial failures, preserves meeting links, and triggers invite refreshes. Respects participant work windows and policies during application and aligns with TimeTether’s existing invite workflows for consistency.

Acceptance Criteria
One-Click Apply Updates All Integrated Calendars
Given an approved rotation proposal covering recurring series across Google Workspace and Microsoft 365 When the owner clicks "Apply" in Rotation Tuner Then recurring event series in all affected calendars are updated to exactly match the proposal’s dates, times (with correct timezones), and attendee rosters And no duplicate or orphan events are created And removed attendees are removed from all future occurrences; added attendees are invited to all future occurrences And each updated series retains its original event UID/SeriesMaster identifier
Atomic Apply with Full Rollback on Partial Failure
Given any provider operation in the apply transaction fails (e.g., rate limit, permission error, network timeout) When the system detects the failure Then all changes made during this apply attempt are rolled back across all providers to the pre-apply state And attendees receive no update or cancellation notifications from the failed attempt And the user sees a single failure message with a correlation ID and list of failed calendars And no calendar remains in a mixed (partially applied) state
Preserve Existing Meeting Links and Conferencing Details
Given affected events contain existing conferencing details (Google Meet, Zoom, Teams) and custom join information When the apply updates times or attendees Then the conferencing provider, meeting URL, meeting ID, and passcodes remain unchanged And custom fields (e.g., agenda URL, room resources) are preserved And new attendees receive invites that include the preserved conferencing details
Invite Refreshes Consistent with Existing Workflow
Given the system’s established invite workflow for updates When apply completes successfully Then each affected attendee receives exactly one update per series according to provider semantics (update vs. reschedule) And ICS/RSVP artifacts are generated using existing templates and branding And attendees with no material changes receive no notification And notification timestamps and organizer addresses match existing workflow conventions
Enforce Participant Work Windows and Policy Guardrails
Given defined participant work windows, time-off, and policy guardrails When the proposal contains any occurrence outside these constraints Then the apply is blocked before any calendar changes are made And the user is shown a list of violating occurrences, participants, and specific policy rules And no calendar updates or notifications are issued until violations are resolved or explicitly overridden per policy
Idempotent Re-Apply Produces No Additional Changes
Given the same approved proposal is applied more than once without changes When the user initiates apply again within 30 days Then no event content changes occur, event version numbers remain unchanged, and no notifications are sent And no write calls are made to provider APIs beyond necessary verification reads
Apply Performance Meets Target for Typical Load
Given a tenant with up to 25 recurring series and 200 total attendees across Google and Microsoft integrations When apply is executed under normal network conditions Then the end-to-end apply completes within 60 seconds 95% of the time and within 120 seconds worst-case And user receives a success confirmation only after all provider acknowledgements are received
Participant Notification & Consent Controls
"As a team member, I want clear notifications and an option to acknowledge or flag issues so that changes don’t surprise me and my constraints are respected."
Description

Automate targeted notifications to impacted participants with concise before/after summaries, rationale grounded in fairness metrics, and one‑click acknowledge or raise‑concern options. Support configurable notice periods, regional message templates, and opt‑in consent requirements for sensitive changes. Feed responses back into the proposal status for go/no‑go gating.

Acceptance Criteria
Targeted Notification Delivery with Before/After Summary
Given a rotation change proposal that affects a subset of participants And regional message templates exist for all affected regions When notifications are generated Then only impacted participants receive a notification via configured channels And each message includes a concise before/after meeting time summary localized to the recipient’s timezone And each message includes a brief rationale citing fairness metric deltas (e.g., projected after-hours minutes change) And no non-impacted participants receive a notification
Configurable Notice Period Enforcement
Given an organization policy defines a minimum notice period of N hours for rotation changes And a proposal has an effective time T When the notification job evaluates the proposal Then if now > T - N hours, the proposal is flagged as requiring explicit consent and is prevented from auto-apply And if now <= T - N hours, notifications are sent and the proposal remains eligible for auto-apply based on response thresholds
One-Click Acknowledge and Raise-Concern
Given a recipient receives a notification with signed single-use action links When they click Acknowledge Then their acknowledgment is recorded with user id, proposal id, and timestamp And the link cannot be reused and expires per policy after E hours And the UI confirms acknowledgment success When they click Raise Concern Then they can submit an optional reason And the concern is recorded with user id, proposal id, and timestamp And the proposal’s auto-apply is paused pending resolution
Consent Threshold Gating for Sensitive Changes
Given a proposal is marked as sensitive and requires opt-in consent from all affected participants or a configured threshold K% When responses are collected Then the system applies the change only when the consent requirement is satisfied before the response deadline D And if the requirement is not satisfied by D, the proposal is set to No-Go and will not be applied And any attempt to override policy guardrails without required authorization is blocked and logged
Regional Template Application and Fallback
Given regional message templates exist for regions R1...Rn and a default template When preparing a notification for a recipient in region Ri Then the system uses the region-specific template and localizes date/time format and language per Ri And if Ri has no template, the default template is used And placeholders for before/after times, timezone names, and fairness metrics render with correct values And preview mode displays the final rendered message for a sample recipient in each region
Response Feed Updates Proposal Go/No-Go
Given a proposal has outstanding notifications and responses arriving over time When the required acknowledgments are received and there are zero open concerns Then the proposal is marked Go and is eligible to be applied at or after the effective time When any concern is open within the notice period Then the proposal is marked No-Go and is blocked from auto-apply until resolved per policy And all status changes and justifications are recorded in the audit log
Change Audit Trail & Rollback
"As a compliance stakeholder, I want a complete audit trail with easy rollback so that we can demonstrate policy adherence and recover from mistakes."
Description

Record every proposal, parameter change, policy decision, user action, and applied calendar change with timestamps, actors, and before/after metrics. Provide searchable history, export, and one‑click rollback to a prior state or schedule snapshot, validating policy compliance during reversal. Integrates with organizational audit systems via webhook/API for compliance reporting.

Acceptance Criteria
Comprehensive Change Event Capture
Given a user or system initiates a proposal, parameter update, policy decision, user-triggered action, or applied calendar change When the action is committed Then an immutable audit event is appended within 1 second containing: event_id (UUIDv4), event_type, timestamp (UTC ISO 8601), actor_id and actor_type (user/service), object_type and object_id, schedule_id, policy_id (if applicable), request_id/correlation_id, outcome (success|failure), error_details (on failure), before_state, after_state, before_metrics and after_metrics (including after_hours_minutes_by_region and rotation_fairness_score) And event_id is unique across the system and the event cannot be edited or deleted post-write (append-only) And the event_type values include at minimum: proposal.created, proposal.updated, parameter.changed, policy.decision.recorded, user.action.invoked, calendar.change.applied, rollback.initiated, rollback.completed, export.requested, export.completed, webhook.delivered, webhook.failed And 99% of audit events are persisted successfully; on persistence failure, the system retries at least 3 times with exponential backoff and surfaces a monitoring alert without blocking the user action
Searchable Audit History
Given at least 50,000 audit events exist across multiple schedules and policies When a user searches by any combination of time range, actor_id, event_type, schedule_id, policy_id, outcome, and keyword (free-text over object_id and error_details) Then the results reflect only matching events with exact filter semantics and support sorting by timestamp asc/desc And the API/UI returns the first page (50 events) within 2 seconds p95 and supports cursor-based pagination without duplicates or omissions And selecting an event reveals full before_state/after_state diffs and before_metrics/after_metrics values And the search activity itself is logged as an audit event (export.requested is not emitted unless an export is initiated)
Audit Log Export (JSONL/CSV)
Given a filtered audit log view or an API request specifying filters and format When the user requests an export in JSONL or CSV Then the system generates a file containing exactly the filtered events, preserving timestamp (UTC), event order (by timestamp desc), and all defined fields And the export is available via a pre-signed link that expires in 24 hours and is scoped to the requesting tenant And for up to 100,000 events the export completes within 60 seconds p95 and the file size and row count match the number of exported events And the export request and completion are recorded as audit events (export.requested, export.completed) including requester and counts And the exported file includes a SHA-256 checksum and metadata (generated_at, filter_summary, field_version)
One-Click Rollback to Prior Snapshot
Given a prior schedule snapshot is selected for a specific schedule_id When the user clicks Rollback and confirms Then a preflight diff displays all changes to be applied (meetings added/removed/shifted) and the impact on after_hours_minutes_by_region and rotation_fairness_score And policy guardrails are evaluated against the target snapshot and the resulting state before any change is applied And if all guardrails pass, the rollback applies atomically; either all calendar updates succeed or the system reverts to the pre-rollback state And the rollback completes within 5 minutes p95 for schedules with up to 500 calendar updates, with progress and final status visible to the user And rollback.initiated and rollback.completed audit events are written with linkage to the source snapshot_id and pre/post metrics And the operation is idempotent via a client-supplied idempotency key; duplicate requests do not produce duplicate changes
Policy Compliance Validation on Rollback
Given active policy guardrails (e.g., after-hours thresholds, fairness rotation bounds) are configured When a rollback would produce a state that violates any active guardrail Then the rollback is blocked and the user is shown a failure summary citing specific policy_id(s), rule(s) violated, and offending metrics And no calendar changes are applied and rollback.completed is not emitted; instead rollback.blocked is recorded with reasons And when the target snapshot is compliant, the system allows the rollback to proceed without requiring manual overrides And all validation inputs (policies, parameters) used in the decision are captured in the associated audit event for traceability
Audit Webhook and Pull API Integration
Given an organization-configured webhook endpoint and API credentials When audit events are generated Then the system delivers outbound webhooks with an HMAC-SHA256 signature, event_id, delivery_id, idempotency_key, and payload matching the audit schema And deliveries meet latency targets (median < 5s, p95 < 20s) with exponential backoff retries for 24 hours on 5xx/network errors and no retries on 2xx And duplicate deliveries are possible; consumers can de-duplicate using idempotency_key and event_id And a pull API allows fetching events by time range and cursor with tenant scoping, returning 100 events per page minimum with stable ordering And webhook.delivered and webhook.failed outcomes are themselves auditable events with response_code, attempt_count, and last_error

Burn Caps

Set per‑person or team fatigue thresholds for a sprint (e.g., max after‑hours occurrences). Forecast‑aware checks warn or block new bookings that would exceed caps and suggest compliant alternatives, protecting wellbeing and policy compliance.

Requirements

Cap Policy Configuration & Inheritance
"As an org admin, I want to define per-team and per-person meeting fatigue caps with clear precedence so that scheduling adheres to wellbeing policies across sprints."
Description

Provide an admin UI and API to define, version, and manage meeting fatigue caps at the organization, team, and person levels. Supported metrics include max after‑hours occurrences per sprint, max early‑start or late‑end counts, max consecutive after‑hours days, and optional weekly sub‑caps within a sprint. Policies support effective dates, sprint cadence alignment, locale/time‑zone aware work windows, exceptions (e.g., on‑call rotations), and blackout dates. Implement explicit precedence rules (person overrides team; multiple team memberships resolved by most restrictive or configured rule) with deterministic evaluation. Include validation, draft/publish states, and import/export for bulk setup. Integrate with TimeTether’s fairness rotation and timezone models so caps evaluate consistently across distributed teams.

Acceptance Criteria
Org-Level Cap Policy Creation & Versioning
Given an org admin in the Cap Policies UI, When they create a new org-scope policy with metrics {maxAfterHoursPerSprint=4, maxEarlyStartPerSprint=2, maxLateEndPerSprint=2, maxConsecutiveAfterHoursDays=2} and set effectiveFrom=2025-10-01 and sprintCadence={length=2w, anchor=Monday, timezone=America/Los_Angeles}, Then the policy is saved as Draft version v1 and is retrievable via API GET /policies?scope=org with all fields persisted. Given the draft exists, When the admin publishes the draft, Then the policy status becomes Published, version remains v1, effectiveFrom is respected, and API returns publishedAt and version metadata. Given a Published v1, When the admin creates a new version from v1 and modifies metrics, Then v2 is created in Draft; publishing v2 deactivates v1 at v2.effectiveFrom or immediately if effectiveFrom is in the past. Given two policies at the same scope have overlapping effective windows, When attempting to publish the second, Then publishing is blocked with error code POLICY_DATE_OVERLAP and the conflict range is included in the error payload.
Team Policy Inheritance & Precedence
Given a Published org policy and a Published team policy for Team Alpha with stricter maxAfterHoursPerSprint=2, When evaluating a Team Alpha member, Then specified metrics use the team values and unspecified metrics inherit from the org policy. Given both org and team define the same metric differently, When evaluating, Then the team value overrides the org value. Given the team policy expires mid-sprint, When evaluating events after the expiry, Then the org policy is used; events before the expiry use the team policy.
Person Override & On-Call Exception
Given a person-level policy for user U with overrides {maxAfterHoursPerSprint=1} and an exception rule {when tag=on_call and window=2025-W41 then maxConsecutiveAfterHoursDays=4}, When EvaluateCaps is called for U for a booking in 2025-W41, Then the exception applies and the cap for consecutive after-hours days is 4 for that week. Given EvaluateCaps is called for U for a booking in a week without tag=on_call, Then caps fall back to the person-level overrides and ignore the exception. Given person-level and team-level values for the same metric, When evaluating, Then the person-level value overrides team and org.
Multiple Team Membership Resolution & Decision Path
Given user U is in Team A (maxAfterHoursPerSprint=3) and Team B (maxAfterHoursPerSprint=1) and org=5, and org setting multiTeamResolution=most_restrictive, When EvaluateCaps runs, Then the resolved value for maxAfterHoursPerSprint is 1. Given org setting multiTeamResolution=priority_order with [Team B, Team A], When EvaluateCaps runs, Then the resolved values come from the first team in the list that defines the metric (Team B). Given deterministic evaluation is required, When EvaluateCaps responds, Then the response includes decisionPath per metric containing {scope, policyId, version, resolutionMode} to prove deterministic precedence application.
Weekly Sub-Caps & Locale/Time-Zone Work Windows
Given a team policy defines weeklySubCaps {maxAfterHoursPerWeek=1} and sprintCadence is 2 weeks anchored Monday, When evaluating week 2 of a sprint, Then no more than 1 after-hours occurrence is allowed in that ISO week regardless of week 1 counts. Given participant P in Asia/Tokyo with work window 09:00–17:00 JST, When an event falls at 20:00 JST for P, Then it is counted as after-hours for P; the same event at 09:00 PT is within hours for a participant in America/Los_Angeles and is not counted as after-hours for that participant. Given TimeTether fairness rotation is enabled, When EvaluateCaps forecasts a recurring series across participants, Then after-hours counts per person are computed using the rotation sequence and each participant's local work window, matching the scheduling simulator totals. Given a blackout date configured for 2025-12-25 for locale=US, When evaluating an event on that date for a US-based participant, Then the event is marked non-bookable and does not contribute to any cap counts.
Sprint Cadence Alignment & Effective Date Boundaries
Given sprintCadence={length=2w, anchor=Monday, timezone=UTC} and a policy with effectiveFrom=2025-10-01 00:00 UTC, When evaluating events across 2025-09-29 to 2025-10-12, Then the first sprint boundary is 2025-09-29 and cap counters reset at 2025-10-13 00:00 UTC. Given effectiveFrom falls mid-sprint, When evaluating, Then the active policy for each event uses the policy effective at the event timestamp; counters do not retroactively recalculate past events before effectiveFrom. Given a policy with effectiveThrough=2025-12-31 23:59:59 and another starting 2026-01-01 00:00:00, When publishing both, Then there is no overlap and evaluation switches exactly at the boundary.
Import/Export Bulk Setup & Validation
Given an admin uploads policies.json via API POST /policies/import with mode=transactional containing 25 policies, When 2 records fail validation, Then 0 policies are created, the response lists the 2 errors with indices and codes, and no side effects occur. Given an admin uploads policies.csv with mode=best_effort containing 10 policies, When 3 rows fail validation, Then 7 policies are created and the response reports 3 row-level errors. Given API GET /policies/export?scope=all, When invoked, Then the response returns all policies with fields {id, scope, metrics, exceptions, blackoutDates, sprintCadence, effectiveFrom, effectiveThrough, version, state} and a checksum; a re-import of the same file in transactional mode results in zero changes.
Forecast‑aware Scheduling Gatekeeper
"As a scheduler, I want the system to automatically warn or block bookings that would exceed someone’s cap so that I don’t inadvertently overload teammates."
Description

Embed a guardrail service in the scheduling flow that simulates the full occurrence set (including recurrences) across the current and upcoming sprint windows for all participants, computes utilization against their applicable caps, and returns an allow/warn/block decision. Support both soft caps (warning with rationale) and hard caps (blocking with rationale), with machine‑readable reason codes and human‑readable explanations. Evaluate against participants’ work windows, time zones, holidays, and fairness rotation constraints. Trigger checks on new bookings, edits to existing series, attendee changes, and time proposals from external calendars. Ensure low‑latency responses and graceful degradation for large series (e.g., up to 52 weekly occurrences). Provide API hooks and UI callbacks for consistent enforcement across web, mobile, and API clients.

Acceptance Criteria
Soft vs Hard Cap Decisioning Across Recurring Series
Given a new recurring meeting request with 10 weekly occurrences within the current and next sprint windows for participants A and B And participant A has a soft cap of 2 after-hours occurrences and a hard cap of 4 for the sprint And the simulation forecasts 3 after-hours occurrences for participant A and no other participant exceeds soft caps When the gatekeeper evaluates the request Then it returns decision = "warn" And includes reasonCodes = ["CAP_SOFT_EXCEEDED"] for participant A And includes a humanReadableExplanation referencing "3 of 2 after-hours occurrences" for participant A Given the same setup and the simulation forecasts 5 after-hours occurrences for participant A When the gatekeeper evaluates the request Then it returns decision = "block" And includes reasonCodes = ["CAP_HARD_EXCEEDED"] for participant A And includes a humanReadableExplanation referencing "5 of 4 after-hours occurrences" for participant A Given the same setup and the simulation forecasts no soft cap exceedance for any participant When the gatekeeper evaluates the request Then it returns decision = "allow" And includes no reasonCodes and no humanReadableExplanations
Gatekeeper Invocations on All Scheduling Touchpoints
Given an action type of create, edit_time, edit_recurrence, attendee_change, or external_proposal on a meeting or series When the action is initiated Then the gatekeeper is invoked with a payload containing the full simulated occurrence set within the current and next sprint windows and all affected participants And the gatekeeper completes evaluation before the scheduling operation is committed And the decision (allow|warn|block), reasonCodes, and explanations are attached to the scheduling transaction audit log
Evaluation Against Work Windows, Time Zones, Holidays, and Fairness Rotation
Rule: Occurrence timestamps are converted to each participant’s local time zone before classification Rule: An occurrence counts toward a participant’s after-hours tally if any part of the meeting time falls outside that participant’s configured work window Rule: Occurrences on a participant’s configured holidays are treated as non-working and count toward after-hours caps for that participant Rule: The configured fairness rotation policy for the series is applied during simulation when attributing after-hours burden per occurrence Rule: The simulation window spans the remainder of the current sprint plus the entirety of the next sprint
Decision Payload Contract with Reason Codes and Explanations
Rule: Response includes fields decision (allow|warn|block), evaluationWindow, participants[], and reasons[] Rule: For decisions warn or block, at least one machine-readable reasonCode is present at the top level and per affected participant Rule: reasonCodes are stable, namespaced, and enumerated, including but not limited to CAP_SOFT_EXCEEDED, CAP_HARD_EXCEEDED, AFTER_HOURS_OCCURRENCE, HOLIDAY_CONFLICT, FAIRNESS_CONSTRAINT Rule: For each reasonCode returned, a humanReadableExplanation string is included and localizable (default locale en-US) with concrete counts (e.g., "3 of 2") Rule: For decision allow, reasons[] MAY be empty and no humanReadableExplanations are required
Low-Latency Responses and Graceful Degradation for Large Series
Given a request with up to 12 simulated occurrences When evaluated under normal load Then p95 end-to-end latency for a synchronous decision is <= 500 ms Given a request with up to 52 simulated occurrences When evaluated under normal load Then p95 end-to-end latency for a synchronous decision is <= 1500 ms Given a request with more than 26 occurrences or transient backpressure When evaluated Then the service may enter degraded mode and return an immediate 202 with processingId within 600 ms And it posts the final decision via the registered callback within 3000 ms p95 And in degraded mode, if the final decision cannot be completed within 5000 ms, the synchronous response is warn with reasonCode = "DEGRADED_EVAL" and coverage >= 80% of occurrences analyzed And degraded mode must never emit a block decision
Consistent Enforcement via API Hooks and UI Callbacks Across Clients
Given identical evaluation inputs from web, mobile, and API clients When the gatekeeper is called Then the decision and reasons are identical across clients Rule: A synchronous Evaluate endpoint accepts idempotencyKey and optional callbackUrl; on block it returns HTTP 422 with structured error including reasonCodes and explanations; on warn/allow it returns HTTP 200 with the decision payload Rule: Server-side persistence enforces the decision; attempts to create or update a series with a block decision are rejected even if a client tries to bypass UI enforcement Rule: Callback deliveries are idempotent (same idempotencyKey yields no duplicate effects) and retried with exponential backoff for at least 24 hours
Compliant Alternative Suggestions on Warn/Block
Given the decision is warn or block When the response is generated Then it includes at least 3 alternative time slots (or all available if fewer) within the evaluated window that keep all participants at or below their caps and respect work windows, time zones, holidays, and fairness rotation And alternatives are sorted by earliest start time then lowest incremental after-hours burden and fairness impact And each alternative includes startUtc, endUtc, participantsImpact[], and a humanReadableExplanation summarizing burden (e.g., "0 after-hours for all participants") And alternative generation adheres to the same latency budgets as the primary evaluation
Cap‑compliant Alternative Suggestions
"As a meeting organizer, I want compliant alternative times automatically suggested when my pick would violate caps so that I can reschedule quickly without trial and error."
Description

When a proposed time breaches one or more caps, generate a ranked set of compliant alternatives that satisfy all participants’ work windows, cap thresholds, fairness rotation rules, duration, and recurring cadence. Prefer minimal‑change options (e.g., nearby times, shifting specific occurrences) and preserve series integrity where possible. Indicate which caps are satisfied for each alternative and the trade‑offs (e.g., organizer after‑hours vs. attendee after‑hours). Support one‑click replace for the entire series or selective occurrences, and expose the suggestions via UI and API with deterministic ordering and pagination. Respect organizer preferences, required resources (rooms/VC), and pre‑existing commitments.

Acceptance Criteria
Cap-Compliant Alternatives Returned
Given a proposed recurring meeting that breaches one or more configured fatigue caps And all participants, their caps, work windows, fairness rotation rules, required resources, and existing commitments are known When the system generates alternative suggestions Then every returned alternative satisfies all participants’ work windows for each occurrence in scope And every returned alternative does not exceed any participant or team cap thresholds for the sprint period And every returned alternative adheres to the specified meeting duration and recurrence cadence And every returned alternative honors configured fairness rotation constraints And no returned alternative overlaps any pre-existing participant commitment or required resource booking And if no compliant alternatives exist, the response contains an empty list with machine-readable reason codes explaining infeasibility
Minimal-Change Ranking and Deterministic Ordering
Given the set of all compliant alternatives When the system ranks alternatives Then alternatives are ordered by minimal change from the original proposal in this priority: 1) nearest times on the proposed day 2) same weekday/time in adjacent weeks within the series 3) selective shifts of conflicting occurrences only 4) broader time shifts within participant work windows And ties are broken deterministically by ISO timestamp, then meeting ID, then lexical participant ID And repeated requests with identical inputs and system state produce identical ordered results
Pagination of Suggestions
Given a deterministic ordered list of suggestions and a page size N When the client requests page 1 and subsequent pages using a server-provided cursor Then each page returns exactly N suggestions except possibly the last page And no suggestion appears on more than one page And the cursor reliably resumes at the next item in the global order And changing page size or page index yields predictable, non-overlapping slices of the same ordered list And total_count and next_cursor are provided when applicable
Preserve Series Integrity and Selective Occurrence Handling
Given a recurring series proposal with one or more occurrences causing cap breaches When generating suggestions Then the system first attempts a single series-wide alternative that keeps a consistent time across all occurrences within the recurrence rules And if no series-wide alternative exists, suggestions include selective occurrence replacements clearly labeled with affected dates And any selective replacement maintains the overall cadence and duration and remains cap-compliant for all participants And each suggestion indicates whether it is series-wide or selective and lists impacted dates And selective replacements do not introduce new conflicts or cap breaches on unaffected occurrences
Trade-Off and Cap Satisfaction Indicators
Given a list of suggested alternatives When the suggestions are returned to the UI and API Then each suggestion includes a machine-readable list of caps satisfied per participant/team (cap type, threshold, current count, remaining headroom) And each suggestion includes a trade-offs summary (e.g., organizer vs. attendee after-hours, fairness rotation impact score) And each suggestion includes a concise ranking rationale And the UI renders a human-readable summary consistent with the machine-readable fields
One-Click Replace for Series or Occurrence
Given a displayed suggestion When the user selects "Replace series" or "Replace selected occurrences" and confirms in a single action Then the system updates the calendar events accordingly, reserves required resources, preserves meeting metadata, and sends updated invites/notifications And the operation is atomic; on any failure, no partial updates are committed and the prior series state is restored And audit logs capture actor, timestamp, target series/occurrences, and suggestion ID And the resulting events are revalidated against caps and constraints at apply time and only committed if compliant
UI/API Parity and Constraint Respect
Given identical inputs (participants, caps, recurrence, preferences, resources, commitments) When suggestions are requested via UI and via API Then both surfaces return the same ordered results, metadata fields, and pagination behavior And organizer preferences (e.g., preferred meeting hours, days) influence feasibility and ranking as configured And required resources (rooms/video) are held or booked for the proposed slots; if unavailable, the alternative is excluded And no suggestion conflicts with any participant’s existing accepted events And API responses conform to the published schema with deterministic field naming and types
Real‑time Cap Status Visualization
"As a scheduler, I want to see each invitee’s cap usage in real time while browsing times so that I can pick a slot that stays within everyone’s limits."
Description

Enhance the scheduling UI to display per‑invitee cap utilization for the active sprint, including counters (consumed/remaining), progress meters, and risk indicators for selected time slots. For recurring series, preview utilization impact across occurrences and highlight the specific ones that would cause breaches. Provide tooltips with explanation of cap rules in effect and links to policy details. Offer filters to show only compliant slots. Ensure accessible design (color contrast, screen‑reader labels) and responsive behavior across devices. Keep visuals in sync with live evaluations from the gatekeeper service and update incrementally as attendees, duration, or cadence change.

Acceptance Criteria
Per‑Invitee Cap Utilization Display for Single Meeting
Given a proposed time slot is selected within the active sprint and invitees are added When the scheduler requests cap evaluations from the gatekeeper Then each invitee shows consumed and remaining counters for each active cap type with numeric values And each invitee shows a progress meter with percent filled and a numeric label aligned to consumed/total And each invitee shows a risk indicator for the selected slot as Safe/At Risk/Will Breach based on gatekeeper evaluation And data reflects the latest evaluation timestamp and appears within 2 seconds of slot selection or attendee change And a pending state is shown until fresh data arrives and no Safe status is displayed until evaluation completes And if the selected slot would exceed any invitee’s cap, the risk indicator is Will Breach and the Schedule action is disabled with an explanation and a link to suggested compliant alternatives (if enabled)
Recurring Series Impact Preview and Breach Highlighting
Given a recurring series with defined cadence and duration is configured within the active sprint When I open the series impact preview Then the UI lists each occurrence with per-invitee deltas applied to cap utilization And occurrences that would cause a breach for any invitee are distinctly highlighted and labeled with the cap type and invitee(s) And totals for consumed/remaining update to include the full series impact and reflect cumulative effects And clicking a highlighted occurrence focuses that date/time in the calendar preview And adjusting cadence, duration, or skipped dates updates the preview within 3 seconds and only the affected occurrences re-render
Compliant Slots Filter
Given the scheduling grid is visible for the selected meeting or series When I enable the Show only compliant slots filter Then only slots that keep all invitees within their caps for the selected occurrence/series remain selectable And the number of compliant slots visible is displayed And if no compliant slots exist, a No compliant slots message is shown with an action to view suggested alternatives And disabling the filter restores all slots to view And enabling the filter triggers a reevaluation with the gatekeeper if the last evaluation is older than 60 seconds
Tooltips and Policy Links for Cap Rules
Given cap utilization elements (counters, meters, risk indicators) are visible When I hover, focus, or tap the info/help affordance Then a tooltip appears within 300 ms describing active cap rules (cap type, thresholds, measurement period, current tally, and effect of the selected slot/series) And the tooltip includes a link to policy details that opens in a new tab/window And the tooltip is keyboard accessible, dismissible with Esc, and announced by screen readers And tooltip content localizes to the user’s locale
Accessibility and Responsive Layout
Given the scheduler is viewed across devices and widths 320px to 1440px+ When cap utilization UI is rendered Then text and icons meet WCAG 2.1 AA contrast (text ≥ 4.5:1; non-text indicators ≥ 3:1) with redundant text labels for color-coded states And all interactive elements are keyboard operable with visible focus indicators and correct ARIA roles/labels (e.g., progressbar with aria-valuenow/min/max; status with aria-live=polite) And screen readers announce each invitee’s cap counters, meter value, and risk state for the current selection concisely And layout reflows without overlap/truncation; touch targets are ≥ 44x44 px And performance remains smooth with no layout shift > 0.1 CLS when updating states
Live Sync with Gatekeeper and Incremental Updates
Given attendees, duration, or cadence are changed for a meeting or series When the change is applied Then the UI requests fresh evaluations and updates only affected invitees and occurrences without full-page refresh And updates arrive within 2 seconds for single-slot changes and within 3 seconds for series changes or a non-blocking progress indicator is shown And stale evaluations older than 60 seconds are automatically invalidated and refreshed And if the gatekeeper errors or times out, an error state is shown, scheduling actions are disabled, and a retry is offered And a Last evaluated timestamp displays and updates after each successful refresh
Notifications, Escalations & Override Workflow
"As a policy owner, I want near-threshold alerts and an approval process for necessary exceptions so that we maintain compliance without blocking critical meetings."
Description

Implement proactive notifications when a user approaches configurable thresholds (e.g., 80% of a cap) and at breach attempts, delivered via email and Slack with deep links to the affected meeting and alternatives. Provide an approval workflow for hard‑cap overrides with auditable reason capture, approver selection (policy owner, manager), expiration windows, and automatic tagging of the booking upon approval. Support escalation rules and SLAs, batched digests, and snooze controls. Synchronize override state with the gatekeeper so subsequent evaluations honor approved exceptions within their validity period.

Acceptance Criteria
Approaching Burn Cap Notification with Deep Links
Given a user has an after-hours cap for the current sprint and their current count reaches at least 80% of that cap without exceeding 100% When a new meeting proposal or update triggers a recalculation of burn counts Then send Slack DM and email to the user within 2 minutes containing the cap type, current count/limit, sprint end date, and deep links to the affected meeting details and compliant alternatives And the deep links resolve to authenticated pages with the correct meeting ID and pre-filtered alternatives in the user’s timezone And deduplicate notifications per user per cap type for 24 hours unless the count increases by at least 1 And record an audit log entry capturing timestamp, channels, recipient IDs, meeting ID, cap metrics, and link targets
Hard Cap Breach Block and Alternatives Suggestion
Given a booking would cause a user or team to exceed a configured hard burn cap in the active sprint When the organizer attempts to finalize the booking via UI or API Then block the booking and display an inline banner showing cap type, current count/limit, and overage amount, plus a Request Override action And return HTTP 409 with error code CAP_HARD_BREACH and machine-readable details via API And present at least 3 compliant alternative slots within the next 10 business days that respect work windows, timezones, and fairness rotation And send Slack/email to the organizer with deep links to the blocked meeting details and the alternatives page
Override Request Submission and Approver Selection
Given a booking is blocked due to a hard cap breach When the organizer clicks Request Override Then display a form requiring a reason (minimum 20 characters) and approver selection (default: policy owner; option: direct manager) And on submit, create an approval request with status Pending and a selected validity scope (specific occurrence[s] or N days from approval) And notify selected approver(s) via Slack/email with approve/deny actions and a deep link to the approval page And persist an audit record with requester, approver(s), reason text, meeting ID, timestamps, validity scope, and request ID
Override Approval, Tagging, and Expiration Enforcement
Given a pending override exists for a blocked booking When an approver approves the request within the configured window Then allow the booking and automatically apply tag Override:<CapType> with the approval ID to the meeting And the approval validity applies only to the specified occurrence(s) and expires at the configured time And before expiration, the gatekeeper bypasses cap checks for covered occurrences; after expiration, standard enforcement resumes And notify the requester of approve/deny outcome and capture decision, approver identity, timestamps, and notes in the audit trail And propagate override state to gatekeeper and scheduling services within 60 seconds of decision
Escalation Rules and SLA Timers
Given an override request remains Pending When 8 business hours elapse without a decision (excluding weekends and configured holidays) Then escalate to the next approver group per policy and notify via Slack/email with high priority And continue to re-notify every 4 business hours until a decision is made, up to 3 escalations And track and display SLA start, escalation events, and stop time on the request And do not auto-approve or auto-deny on SLA breach; the request remains actionable until decided
Batched Digests and Snooze Controls
Given a user has non-critical notifications (approaching caps, pending overrides not near SLA breach) When the daily digest time occurs at 17:00 local time Then send a single digest via email and Slack summarizing all relevant items from the prior 24 hours And allow the user to snooze individual threads for 24, 48, or 72 hours; snoozed items suppress realtime notifications but remain in the digest marked Snoozed And critical events (hard cap breaches, SLA escalations) bypass snooze and send immediately And persist notification preferences and snooze states; apply changes within 2 minutes across devices
Gatekeeper Synchronizes and Honors Overrides in Counts
Given an approved override covers two specified occurrences in a recurring series that would otherwise exceed the cap When the system evaluates the next three occurrences Then the first two occur without warnings/blocks and do not increment cap counts; the third reverts to standard enforcement And the gatekeeper returns decision code EXEMPT for covered occurrences and ENFORCE for others And all services reflect consistent counts and override status within 60 seconds of approval with no double-counting
Compliance Reporting & Audit Log
"As a compliance analyst, I want reports and a full audit trail of cap decisions and overrides so that I can track adherence and identify systemic issues."
Description

Create an immutable audit log of cap evaluations, warnings, blocks, suggestions shown, and overrides, including who/when/why, cap definitions in effect, meeting identifiers, and decision outcomes. Provide reporting dashboards by team, sprint, and user to track utilization, breach rates, overrides, and after‑hours reductions over time. Offer CSV export and a read API with filters for time range, cap type, and decision. Implement data retention controls, PII minimization, and role‑based access to sensitive details. Integrate with TimeTether analytics to attribute impact on after‑hours reduction and equitable load distribution.

Acceptance Criteria
Immutable Audit Log for Burn Cap Decisions
- Given a scheduling action triggers a burn cap evaluation, When the system processes the request, Then an append-only audit entry is recorded with fields: tenant_id, event_type ∈ {evaluation, warning, block, suggestion_shown, override}, meeting_id, team_id, sprint_id, affected_user_ids, cap_type, cap_threshold, cap_window, evaluation_inputs_ref, decision_outcome, actor_id/service_id, actor_role, reason/explanation, request_id, cap_definition_version, created_at (ISO-8601 UTC), sequence_id. - Given an audit entry is created, When any update or delete is attempted via any interface, Then the operation is rejected with 405 and a tamper-attempt event is logged. - Given a set of consecutive audit entries, When integrity verification runs, Then a hash chain/signature validates all entries and order via sequence_id with 0 mismatches. - Given the same request_id is processed more than once, When audit entries are written, Then exactly one entry exists per unique request_id and duplicates are not created. - Given multiple tenants exist, When querying the log, Then entries are isolated by tenant_id and cross-tenant access is impossible (403).
Role-Based Access to Audit Data
- Given a user with role Admin (org), When accessing audit log, dashboards, export, or API, Then they can view full details for all teams within their tenant. - Given a user with role Manager (team-scoped), When accessing data, Then they can view only their teams’ entries; PII beyond user_id/team_id is redacted; cross-team access returns 403. - Given a user with role IC (individual contributor), When accessing data, Then they can view only entries where they are a participant; sensitive fields (reason text, evaluation_inputs_ref) are redacted. - Given a user with role Auditor (read-only), When accessing data, Then they can view cross-team data with PII minimized and cannot modify settings or download exports containing unredacted PII. - Given permissions are updated, When the change is saved, Then effective access changes take effect within 5 minutes and are reflected in subsequent API/dashboard requests. - Given any unauthorized access attempt, When it occurs, Then the request is denied with 403 and an access_denied audit event is recorded including requester and scope.
PII Minimization and Redaction Controls
- Given audit logging is enabled, When entries are stored, Then only pseudonymous identifiers (tenant_id, team_id, user_id, meeting_id) and decision metadata are persisted; names, emails, calendar titles/descriptions, and free-text notes are not stored. - Given a user without PII-unlock scope, When exporting CSV or calling the read API, Then PII fields remain redacted or omitted and cannot be reconstructed from identifiers alone. - Given a tenant privacy configuration, When PII minimization level is set (e.g., Strict/Standard), Then responses and exports reflect the setting immediately without requiring data re-write. - Given a user with explicit PII-unlock scope and approved justification, When accessing data, Then de-redaction is allowed and the access is itself audited with who/when/why. - Given data classification policies, When inspecting any field, Then each field has a classification label (Public/Internal/Sensitive) available via schema metadata endpoint and used to drive redaction.
Reporting Dashboards for Utilization, Breaches, and Impact Attribution
- Given a selected time range or sprint, When loading the Compliance dashboard, Then it displays by team and user: cap utilization %, count of warnings, blocks, overrides, breach rate, after-hours occurrences, and after-hours reduction vs baseline. - Given the fairness objective, When viewing the Equity panel, Then it shows an equitable load distribution index (e.g., Gini or standard deviation of after-hours occurrences) and trend over time. - Given filters for cap_type and decision, When applied, Then all widgets, tables, and totals update consistently and within 5 seconds p95. - Given a user drills from org -> team -> user, When selecting a data point, Then the dashboard provides drill-down to the underlying audit entries with matching counts (±1%). - Given integration with TimeTether analytics, When attribution is computed, Then the dashboard shows attributable after-hours reduction (%) to Burn Caps with methodology link and reproducible query ID. - Given data freshness SLA, When checked at any time, Then dashboard data is no older than 15 minutes from event ingestion.
CSV Export with Filters and Compliance Safeguards
- Given a user with export permission, When exporting audit data, Then the CSV includes a header row and columns: created_at (UTC ISO-8601), tenant_id, team_id, sprint_id, user_id(s), meeting_id, event_type, cap_type, cap_threshold, cap_window, decision_outcome, actor_role, reason_summary, request_id, cap_definition_version, sequence_id. - Given filters for time range, cap_type, decision, team_id, sprint_id, When set, Then the exported rows match the same filtered results as the dashboard/API (row count parity ±0%). - Given large datasets up to 1,000,000 rows, When export is requested, Then a streamed ZIP-compressed CSV is delivered within 60 seconds or an async job with download link within 5 minutes, including SHA-256 checksum. - Given RBAC and PII minimization, When exporting, Then redactions and scoping are enforced identically to on-screen data; attempts to bypass are denied and audited. - Given timestamp fields, When inspected, Then all timestamps are in UTC ISO-8601 and numeric IDs preserve leading zeros; delimiter is comma and columns are in stable documented order.
Read API for Audit Log and Metrics
- Given a client with OAuth2 scope audit.read, When calling GET /audit-log with filters (start_time, end_time, cap_type, decision, team_id, sprint_id, user_id), Then the API returns matching entries with cursor-based pagination (limit ≤ 1000) and sort by created_at asc/desc. - Given a client with scope metrics.read, When calling GET /metrics with the same filters, Then the API returns aggregated metrics (utilization, warnings, blocks, overrides, breach rate, after-hours reduction, equity index) and dimensions (team, sprint, user). - Given invalid or missing scopes, When requests are made, Then the API responds 401/403 with no data leakage. - Given typical query complexity, When executed, Then p95 latency < 500 ms for ≤ 10k-row scans; long-running queries require pagination or async jobs. - Given caching headers, When repeating identical requests, Then ETag/Last-Modified enables 304 responses; results are consistent and idempotent across retries. - Given API schema stability, When clients validate against OpenAPI, Then responses conform to the published schema version and include deprecation headers when applicable.
Data Retention, Purge, and Legal Hold
- Given tenant retention settings, When configured (e.g., raw logs: 180 days; aggregates: 24 months), Then the system enforces these durations automatically without manual intervention. - Given the daily retention job, When it runs, Then expired raw audit entries are purged, aggregates are preserved, and a purge_summary audit event is recorded with counts and ranges. - Given a legal hold is placed on a tenant/team/user, When the retention job runs, Then affected records are excluded from purge until the hold is removed; all hold changes are audited. - Given a user requests data deletion, When the purge is complete, Then dashboards and API exclude purged records and totals reconcile within 0.5% of pre-purge aggregates. - Given privacy-by-design, When purging PII, Then no residual or derived data enables re-identification; verification scans report 0 violations. - Given backfill is required, When rebuilding aggregates from retained data, Then metrics match prior values within 0.5% variance and are tagged with backfill_run_id.

Recovery Buffer

When the forecast flags intense stretches, TimeTether recommends recovery windows and no‑meeting holds for affected people. It proposes moving non‑critical sessions or creating follow‑the‑sun duplicates so teams can recharge without losing momentum.

Requirements

Intensity Detection & Thresholding
"As a team lead, I want the system to detect intense calendar stretches so that it can proactively recommend recovery windows before burnout risks increase."
Description

Implements configurable detection of intense stretches by analyzing forecasted workload, meeting density, after-hours risk, and context-switch frequency across time zones. Ingests signals from TimeTether’s forecasting engine and calendar integrations to score upcoming periods per person and per team. Applies tunable thresholds and cooldown windows to determine when recovery buffers should be considered, generating machine-readable flags that downstream components use to propose holds or reschedules. Ensures minimal false positives via sensitivity controls and supports per-team overrides to align with differing operating rhythms.

Acceptance Criteria
Signal Ingestion and Normalization
Given TimeTether forecasting and calendar APIs are reachable When the hourly scoring job starts Then the system ingests new or updated workload, meeting density, after-hours risk, and context-switch signals updated within the last 60 minutes for all active users and teams Given varying source units and time zones When signals are ingested Then values are normalized to 0–1 per documented transform and all timestamps are stored as ISO 8601 UTC with source timezone captured in metadata Given a signal for a user is missing or stale for more than 90 minutes When scoring executes Then the system imputes the configured default for that signal, marks it as "imputed" in metadata, and proceeds without failing the job Given provider rate limits are reached When ingestion would exceed quotas Then the system retries with exponential backoff up to 3 attempts and marks only affected entities as "partial" without blocking others
Per-Person Intensity Scoring Across Time Zones
Given a configurable time-window size (default 30 minutes) and weights per signal When a scoring run executes Then the system computes a per-person intensity score in the range 0–100 for each window over the next 14 days using the weighted normalization defined in the scoring specification Given a user’s local work window and timezone, including daylight saving time changes When calculating after-hours risk Then windows outside configured work hours are treated as after-hours for that user even across DST transitions Given overlapping meetings and topic context switches When computing meeting density and context-switch frequency Then overlaps and switches within each window are reflected per specification and contribute to the final score
Per-Team Aggregated Intensity Scoring
Given a team with N active members and per-person scores computed When calculating the team intensity score for a window Then the team score equals the configured percentile of member scores (default P75), excluding members marked PTO or out-of-office Given sub-teams exist When aggregation is computed for a parent team Then the calculation includes members of all descendant teams unless specifically excluded by configuration Given a tie or insufficient member data (fewer than 2 members with valid scores) When computing team score Then apply fallback: compute median of available scores; if none available, mark window as "insufficient_data" and do not trigger detection
Threshold Triggering with Cooldown and Hysteresis
Given threshold T_high, release threshold T_low, minimum duration D windows, and cooldown C minutes When a person or team score is greater than or equal to T_high for at least D consecutive windows Then an intensity_exceeded event is opened for that entity and the cooldown timer starts Given an open event and cooldown C is active When additional windows remain greater than or equal to T_high Then the existing event is extended without emitting duplicate events Given an open event When scores drop below T_low for at least D consecutive windows Then the event is closed and cooldown continues to prevent new events until C minutes have elapsed since close Given a configuration change to thresholds When the next scoring run occurs Then new thresholds are applied prospectively and logged with a configuration version stamp
Machine-Readable Flag Emission
Given an intensity event is opened or closed When the detection run completes Then a flag is emitted within 60 seconds to the message bus with schema fields: event_id, entity_type (person|team), entity_id, window_start, window_end, score_at_trigger, threshold_used, sensitivity_level, source_version, imputation_ratio, timezone, and correlation_id Given the flag payload When validated against the JSON Schema v1 for intensity flags Then validation passes for 100% of emitted messages and payload size is less than or equal to 8 KB Given retries or duplicate computations When emitting flags Then messages are idempotent by event_id and are de-duplicated by subscribers
Sensitivity Controls and False-Positive Minimization
Given sensitivity profiles low, medium, and high with predefined thresholds and minimum duration D When switching profiles at the organization level Then detection behavior updates on the next run and the active profile is included in emitted flags Given a QA-provided labeled validation dataset of intense versus not-intense windows When running detection with the medium profile Then precision is greater than or equal to 0.80 and recall is greater than or equal to 0.60 on that dataset; with the low profile, precision is greater than or equal to 0.90; with the high profile, recall is greater than or equal to 0.75 Given manual per-signal weight adjustments by an admin When weights are changed Then the resulting ROC AUC on the validation dataset does not degrade by more than 0.02 compared to the previous configuration or the system warns and requires confirmation
Per-Team Overrides and Precedence
Given organization-level defaults and team-level overrides for thresholds, cooldown, window size, and aggregation percentile When computing scores for members of that team Then team overrides take precedence over organization defaults Given user-level overrides exist When computing scores for that user Then precedence is user override over team override over organization default Given a change to any override When the change is saved Then the new configuration takes effect within 5 minutes, is audit-logged with actor, old_value, new_value, and reason, and is available via the configuration API
Recovery Window Recommendation Engine
"As a distributed team lead, I want automatic suggestions for recovery windows and no-meeting holds that respect time zones and work windows so that my team can recharge without disrupting critical work."
Description

Generates personalized, conflict-free recovery windows and no-meeting holds for affected individuals and teams based on intensity signals, time zones, work-hour preferences, PTO, and cross-functional dependencies. Computes optimal durations and placements that minimize disruption to critical paths while maximizing contiguous recharge time. Produces a ranked set of options with rationale, highlights trade-offs, and supports batch suggestions for entire sprints or weeks. Integrates with fairness rotation logic to distribute holds equitably and emits structured proposals consumable by UI, notifications, and scheduling automations.

Acceptance Criteria
Generate Conflict-Free Recovery Windows Per Person
Given a person with forecasted intense stretches within a date range and defined work-hour preferences, time zone (IANA), PTO, and existing meetings with critical-path flags When the engine generates recovery recommendations Then it returns at least one recovery window per affected intense stretch, or an explicit "no feasible window" reason And every proposed window is fully within the person's stated work hours and outside PTO And no proposed window overlaps existing events or critical-path dependencies And each window is contiguous and has a duration within the configured min/max bounds And time computations respect the person's IANA time zone
Ranked Options With Rationale and Trade-offs
Given a set of feasible recovery windows for an individual or team When the engine produces recommendations Then it returns 3 to 5 options ranked by a numeric score (highest first) And each option includes: score, contiguous_minutes, meetings_moved_count, participants_impacted_count, and a rationale summary And each option includes a trade_offs array describing at least two explicit trade-offs (e.g., fewer moves vs. shorter contiguous time) And the score monotonically increases with contiguous_minutes and decreases with disruption_cost across the returned options And ranking ties are resolved deterministically using timestamp then option_id
Fairness Rotation Across Team Holds
Given a team with an existing fairness rotation state for the current sprint/week and multiple eligible candidates for holds When the engine assigns holds Then the resulting hold_count_range (max - min holds per person) for the sprint does not increase by more than 1 And the engine outputs fairness metadata per person (prior_holds, proposed_holds, delta) And if two candidates remain tied after constraints, the selection alternates based on last_assigned_order
Batch Suggestions For Sprint or Week
Given a date range covering a sprint or calendar week and a set of teams When the engine runs in batch mode Then it identifies all people with forecasted intense stretches within the range And produces proposals for at least 95% of affected people, or provides explicit reasons per omission (e.g., no feasible window, PTO-only week) And completes within 60 seconds for up to 200 affected people And returns a batch summary including counts: affected, proposed, moved_meetings, no_feasible, by_team
Propose No-Meeting Holds and Session Adjustments
Given non-critical sessions that conflict with a proposed recovery window When generating proposals Then the engine creates a no_meeting_hold for the window with scope (person|team), label, and recurrence if applicable And it proposes moving only sessions marked non-critical or creating follow-the-sun duplicates when a single cross-timezone slot is infeasible And proposed moves/duplicates keep all required participants within their stated work windows and outside PTO And the output lists sessions to move or duplicate with target times and impacted participants
Structured Proposal Output For UI, Notifications, and Automations
Given generated recommendations When emitting results Then proposals conform to the v1 schema with fields: proposal_id, subject_type (person|team), time_zone (IANA), windows[], options[], rationale, trade_offs[], fairness, actions[], and correlation_id And all timestamps are ISO 8601 with timezone offsets; durations are integers in minutes And every action item includes action_type (hold|move|duplicate), target_ids, suggested_time, and required_approvals And the payload is idempotent such that re-emitting the same proposal_id does not create duplicates
Respect Cross-Functional Dependencies and Critical Paths
Given meetings tagged as critical_path or dependency_blocker across teams When proposing holds and moves Then no proposal delays a critical_path milestone beyond its due date And dependency_blocker meetings are excluded from move lists by default And the engine outputs an impact analysis for each option listing impacted dependencies (must be zero for any auto-approved option) And any option that would impact a dependency is flagged requires_approval and ranked below all zero-impact options
Auto Reschedule Non-Critical Meetings
"As a product manager, I want non-critical meetings automatically moved out of intense periods so that the calendar remains manageable and focus time is preserved."
Description

Identifies non-critical sessions within intense periods using labels, meeting heuristics, and organizer-provided priorities, then proposes or executes reschedules into lower-intensity windows. Preserves attendee constraints, avoids after-hours drift, and maintains sequence dependencies (e.g., standups before planning). Offers batch reschedule previews with impact diffs and one-click application, with automatic updates to invites and agendas. Falls back gracefully when no viable slots exist by recommending async alternatives or follow-the-sun duplicates.

Acceptance Criteria
Identify Non-Critical Meetings During Intense Periods
Given a forecasted intense period is flagged for the team And meetings have system labels, heuristic scores, and organizer-provided priorities When the identification process runs Then meetings explicitly marked critical by the organizer are excluded from candidates And meetings whose combined indicators meet the non-critical classification rules are selected as candidates And a justification is stored per candidate referencing label/heuristic/priority inputs And the result set is reproducible from stored inputs and rules
Propose Lower-Intensity, Constraint-Respecting Reschedule Options
Given identified non-critical candidate meetings and attendees’ timezones, work windows, and holds When candidate slots are searched Then only slots with lower intensity than the current slot are proposed And proposed slots fall within all required attendees’ work windows and respect no-meeting holds And proposals introduce no conflicts for required attendees And proposals preserve configured sequence dependencies And at least one proposal per meeting is returned or the meeting is marked as no viable slot
Maintain Sequence Dependencies Across Reschedules
Given dependency rules (e.g., Standup precedes Planning on the same day) When generating or applying reschedules for dependent meetings Then the resulting schedule preserves all dependency constraints for the affected period And any proposal that would break a dependency is suppressed with an explanation And if no compliant proposal exists, the meeting is routed to fallback
Batch Preview With Impact Diffs and One-Click Apply
Given a set of candidate reschedules is selected for preview When the preview is generated Then each meeting shows before/after time, intensity delta, attendees impacted, and per-attendee time shift And aggregate metrics display total meetings moved, after-hours events delta (does not increase), total outside-hours minutes delta (does not increase), and introduced conflicts (equals zero) And the organizer can apply the batch with one click to schedule the selected options
Execute Reschedule and Update Invites and Agendas
Given the organizer applies a batch of selected reschedules When execution runs Then calendar events are updated to new times with updated invites sent to all attendees And linked agendas are updated to reflect new dates/times without content loss And meeting links and attachments are preserved unless an explicit venue change is selected And each attendee receives a single consolidated notification per batch
Fallback to Async or Follow-the-Sun Duplicates When No Viable Slots Exist
Given a meeting has no reschedule proposal that satisfies constraints and dependencies When fallback is initiated Then the system recommends either an async alternative (e.g., doc/thread/recording) or a follow-the-sun duplicate by region And fallback proposals specify required participants, target windows, and delivery deadlines And accepting a fallback with one click creates the placeholder event(s) and sends notifications
Honor Recovery Windows and No-Meeting Holds
Given recovery windows and no-meeting holds are in effect for affected attendees When generating proposals and executing reschedules Then no rescheduled meeting is placed within any recovery window or no-meeting hold for required attendees And proposals violating optional attendees’ holds are flagged with opt-in required And preview metrics reflect preserved recovery time
Follow-the-Sun Session Duplication
"As an engineering manager of multi-region teams, I want duplicate sessions scheduled for different regions so that momentum continues without forcing after-hours attendance."
Description

Creates region-specific duplicate sessions for recurring meetings during recovery holds to maintain momentum without requiring after-hours attendance. Aligns each duplicate with local work windows and ensures content parity through shared agendas and synced notes. Coordinates presenters, recordings, and handoffs between regions, and prevents double-booking for cross-region participants. Provides organizers with a consolidated view linking duplicates as a single series for metrics and governance.

Acceptance Criteria
Region-Specific Duplicate Creation During Recovery Holds
Given a recurring meeting with participants across at least two regions and an active recovery hold for Region A, When the next occurrence intersects the hold, Then the system creates a Region A duplicate for that occurrence within 10 minutes of hold activation and tags it with the region. Given the duplicate creation, When invites are sent, Then only Region A participants and required cross-region roles are included, and the original occurrence keeps non-Region A invitees. Given event creation, When checking the calendar, Then the duplicate inherits the series name with a region suffix and links back to the consolidated series.
Local Work Window Alignment
Given region-specific working hours configured per participant, When scheduling the duplicate, Then the start time falls within all required participants' work windows and within the organizer's allowed hours, with no start earlier than the earliest window nor end later than the latest window. Given no common slot in the standard cadence week, When evaluation occurs, Then the system proposes the nearest slot within 5 business days and flags the exception to the organizer. Given a daylight saving transition on the occurrence date, When computing windows, Then local time offsets are applied correctly and the meeting remains within work windows.
Content Parity via Shared Agenda and Synced Notes
Given a shared agenda and notes artifact template for the series, When a duplicate is created, Then the duplicate references the same agenda link and editable notes doc, not a static copy. Given any update to the agenda or notes in any duplicate, When the change is saved, Then the update is visible in all duplicates within 60 seconds. Given attachments are added to one duplicate, When viewing another duplicate or the series, Then those attachments appear in a consolidated artifacts list with region tags.
Presenter Coordination and Handoff Workflow
Given each region has a designated presenter pool, When duplicates are created, Then a local presenter is auto-assigned to each region’s duplicate or an escalation is sent if none are available. Given an earlier-region duplicate ends, When its recording and notes finalize, Then a handoff summary is generated and delivered to the next region at least 15 minutes before their start time. Given an assigned presenter declines or becomes unavailable, When the system detects the change, Then an alternate is auto-assigned from the region pool and the organizer is notified within 5 minutes.
Recording and Artifact Synchronization
Given recording is enabled for the series, When a duplicate session starts, Then auto-recording is enabled and the recording link is attached to the consolidated series within 15 minutes after meeting end. Given transcripts and action items are generated, When post-processing completes, Then decisions and actions from all duplicates are merged into a single shared log with region tags and deduplicated entries. Given any artifact (agenda, notes, recording) is updated or replaced, When synchronization runs, Then the consolidated series reflects the latest version without breaking existing links.
Cross-Region Double-Booking Prevention
Given a participant belongs to multiple regions or is marked as global, When duplicates for the same occurrence are scheduled, Then the participant is invited to only one duplicate unless explicitly marked as Required Across Regions. Given a participant is already invited to one duplicate, When another duplicate would overlap, Then the system declines the second on behalf of the participant and adds an FYI link to the consolidated series. Given participant work windows, When assessing availability, Then no duplicate invitation is issued outside the participant’s work window unless the participant has opted into after-hours exceptions.
Consolidated Series View, Governance, and Metrics
Given multiple region duplicates exist for an occurrence, When the organizer opens the series view, Then all duplicates are displayed on a single timeline with region labels and interlinked navigation. Given attendance, recordings, decisions, and action items across duplicates, When nightly metrics compute, Then the dashboard aggregates per-duplicate and total metrics including attendance rate by region and after-hours reduction percentage. Given the organizer cancels or reschedules one duplicate, When the change is confirmed, Then linked duplicates remain unchanged unless explicitly selected, and the consolidated audit log records the action with scope and timestamp.
One-Click Holds & Approval Workflow
"As a meeting organizer, I want a one-click approval flow to apply holds and notify attendees so that changes are applied quickly and transparently."
Description

Delivers an approval flow that routes recovery window proposals to affected users and organizers via app UI and notifications. Supports one-click acceptance, partial acceptance (per meeting or per person), and smart conflict resolution. On approval, applies calendar holds across integrated providers, updates series rules, notifies attendees with rationale, and logs changes for audit. Includes rollback within a grace period and SLA-based reminders for pending approvals to ensure timely application ahead of intense stretches.

Acceptance Criteria
Proposal Routing & SLA-Based Reminders
Given a forecasted intense stretch for a team within the next 14 days When TimeTether generates recovery window proposals Then all affected users and relevant organizers receive an in-app approval item and notification via their configured channels within 5 minutes And each notification includes proposed recovery windows, impacted meeting count, earliest effective time, and one-click links for Accept All, Partial Accept, Decline, and View Details And notifications respect user preferences and quiet hours And duplicate suppression ensures at most one active notification per proposal per recipient And a unique approval token is embedded to prevent replay and expires at the start of the intense stretch or upon proposal withdrawal And if no action is taken, reminders are sent per SLA: initial reminder at T0-24h, escalation to organizers at T0-12h, and final high-priority reminder at T0-4h And reminders cease immediately upon any recipient action or if the forecast is withdrawn And delivery success rate is ≥ 99% within 10 minutes, with failures retried up to 3 times with exponential backoff
One-Click Full Acceptance
Given a recipient with permission opens the proposal When they select Accept All and confirm Then the system records the approval with user, scope=all, and timestamp And disables duplicate actions for that proposal by that user And computes the application plan and applies holds within 60 seconds And updates the proposal UI to Accepted and shows success within 5 seconds And emits an ApprovalAccepted event for downstream processing And the action is idempotent; retrying does not create duplicate changes
Partial Acceptance (Per Meeting and Per Person)
Given a recipient opens the proposal with options to select specific meetings and/or specific people When they select a subset of meetings and/or toggle acceptance per person and confirm Then only the selected meetings and selected people are included in the approval scope; all others remain unchanged And the confirmation modal shows an accurate count of meetings and people to be affected before submission And role-based access control is enforced (organizers can act for their meetings; individuals can act for themselves) And the system persists the granular selections in the audit trail and reflects them in the resulting changes And the action is idempotent and can be edited by submitting a new partial acceptance that supersedes the prior one
Smart Conflict Resolution on Approval
Given approval (full or partial) would cause scheduling conflicts or after-hours impact When the system computes the application plan Then critical meetings (as flagged by policy) are not moved And non-critical meetings are moved to the earliest available slots within overlapping work windows within 7 days And if no feasible shared slot exists within 7 days, the system proposes follow-the-sun duplicates partitioned by timezone cohorts per policy And no participant is double-booked, and after-hours minutes per participant do not increase beyond the baseline target for the week And a conflict resolution summary is presented to the approver prior to final apply; if constraints cannot be satisfied, the apply is blocked with a clear error And the plan computation completes within 15 seconds for up to 200 impacted events
Cross-Provider Hold Application & Series Update
Given an approval is confirmed When holds are applied across connected calendar providers Then Recovery Hold events are created on each affected person’s primary work calendar at the specified windows, marked Busy, with a descriptive title and rationale link And holds use the correct local timezone and do not overlap existing holds And impacted recurring meetings have their series updated via RRULE exceptions or per-occurrence modifications to avoid overlap with holds And changes are applied atomically per person; on any provider error, all changes for that person are rolled back and the approver is notified And operations are retried up to 3 times with exponential backoff and are idempotent (no duplicate holds) And application completes within 60 seconds for up to 50 attendees across providers
Post-Approval Attendee Notifications with Rationale
Given an approval has been successfully applied When meetings are moved, duplicated, or skipped due to recovery holds Then all attendees of impacted meetings are notified within 5 minutes in their preferred channels And notifications include a clear rationale referencing the intense stretch forecast and expected recovery benefit, a summary of changes, and a deep link to details And content is personalized per attendee (only their changes) and respects visibility/privacy settings; no private details are leaked to external attendees And duplicate notifications are suppressed and message delivery is tracked with success/failure metrics
Audit Logging and Rollback Grace Period
Given any approval action (accept all, partial accept, decline, apply, rollback) When the action occurs Then an immutable audit entry is recorded with actor, timestamp, proposal ID, scope (all/meetings/people), before/after states, provider change IDs, and rationale And audit entries are searchable by proposal ID, meeting ID, and user ID And a rollback option is available to proposers and approvers for 15 minutes after apply And on rollback, all calendar changes are reverted, series updates are restored, attendees receive correction notices, and the proposal status becomes Rolled Back And rollback is atomic and idempotent; partial failures trigger automatic retries and final error visibility
Admin Policy & Fairness Controls
"As a workspace admin, I want to configure thresholds, fairness rules, and hold caps so that recovery buffers align with company policy and remain equitable."
Description

Provides admin-configurable policies that govern detection sensitivity, minimum/maximum recovery durations, weekly hold caps per person, blackout dates, and exemption rules for critical roles. Integrates fairness-driven rotation to balance who absorbs reschedules and who receives holds, with per-team and company-level overrides. Exposes audit logs, policy versioning, and simulation mode to preview impact before rollout. Ensures compliance with regional work-time regulations and company SLAs.

Acceptance Criteria
Admin configures detection sensitivity by team and company
Given an admin with policy permissions When the admin sets detection sensitivity thresholds at team and company levels and saves Then the system validates values within the allowed range (0.0–1.0) and shows effective precedence (team overrides company) And the active thresholds are applied on the next forecast run to flag intense stretches per scope And a confirmation summarizes updated scopes and effective values
Enforce min/max recovery durations in recommendations
Given min and max recovery durations are configured per team and at company default When the system generates recovery window recommendations Then each recommendation duration falls within the applicable [min, max] bounds And recommendations outside bounds are not produced And the UI/API surface the applied scope (team/company) and values used for each recommendation
Apply weekly hold caps per person with fairness rotation
Given a weekly hold cap per person is configured and fairness rotation is enabled When the system allocates no‑meeting holds for a given week Then no individual receives more holds than the configured cap And if a candidate has reached the cap, the system selects the next eligible person per fairness rotation within the team And the rotation ledger updates atomically and an audit entry records the assignment and reason
Honor blackout dates and regional work-time regulations
Given company blackout dates, regional calendars, and work‑time regulations are configured When proposing holds or reschedules Then no holds/reschedules are scheduled on blackout dates or outside legal work windows for each participant’s locale And if no compliant slot exists, the system proposes a follow‑the‑sun duplicate or marks the item for manual review And the decision record references the specific regulation or blackout that blocked scheduling
Exempt critical roles from holds/reschedules with override
Given exemption rules identify critical roles and/or named users When generating holds and reschedule assignments Then exempt users are not assigned holds/reschedules by default And if an admin applies an explicit override on a specific event, the system requires an override reason and records actor, timestamp, and scope in the audit log And exemption changes take effect immediately for subsequent forecasts
Maintain audit logs and policy versioning for changes and decisions
Given policy changes and scheduling decisions occur When a policy is created, updated, published, rolled back, or a fairness assignment is made Then an immutable audit entry is stored with timestamp, actor, action, affected scope, previous/new values, and policy version ID And every recommendation/decision references the policy version used And admins can view version history, export logs, and rollback to a prior version without data loss
Run simulation mode to preview impact without production changes
Given an admin selects Simulation Mode with a draft policy version and a time horizon When the system runs the simulation Then it produces a report including projected recovery windows, total after‑hours minutes delta, reschedules count, holds‑per‑person distribution, fairness metrics per team, and avoided SLA/regulatory violations And no invites, calendar holds, or notifications are sent And admins can compare simulation vs. current policy and choose Publish or Discard

DST Shield

Anticipates daylight‑saving shifts and regional holidays that could turn normal meetings into after‑hours. Automatically recalibrates the forecast and offers preemptive adjustments so no one gets blindsided by the clock change.

Requirements

Global DST & Holiday Data Pipeline
"As a scheduling admin, I want TimeTether to reliably know upcoming DST shifts and holidays across all regions so that meetings are scheduled with accurate local context."
Description

Continuously ingest and maintain authoritative daylight-saving time transitions and regional/public holidays for all user time zones and locales. Use IANA tzdb for DST boundaries and integrate multiple holiday sources (ICS feeds and public APIs) with de-duplication, precedence rules, and per-user locale mapping. Normalize and version the data, support org/team/user-level overrides, and expose a service returning at least 18 months of transitions/holidays with metadata (jurisdiction, observance rules, partial/half-day flags). Refresh data daily and on upstream changes, cache for low-latency queries, and emit change events that trigger re-forecasting of impacted meeting series. Provide resiliency with provider failover, backfill jobs, and integrity checks to ensure consistent scheduling inputs.

Acceptance Criteria
IANA tzdb DST Transition Ingestion and Normalization
Given the latest IANA tzdb release is available When the nightly ingest job executes Then transitions for all IANA zones referenced by active users are imported and normalized for a rolling 18-month horizon And each transition record includes zone, UTC offset before/after, effective instant, version, source="IANA", and last_updated timestamp And integrity checks pass (no overlapping or missing transitions; offsets change only at transition instants) And reruns are idempotent and do not create duplicate records
Multi-Source Holiday Aggregation with De-duplication and Precedence
Given configured ICS feeds and public holiday APIs for mapped jurisdictions When the aggregation job runs Then holidays for the next 18 months are collected from all sources And duplicates are merged using precedence: internal-curated > ICS > API And each holiday includes metadata: jurisdiction, canonical name, date span, partial_day flag (0, 0.5, 1), observance rules, source, and version And conflicting observance dates are resolved by precedence with an auditable decision record And per-user locale mapping selects exactly one jurisdiction set per user with a deterministic fallback when ambiguous
Daily and Upstream-Triggered Refresh with Versioning
Given the system is operational When 24 hours elapse or an upstream change (webhook or ETag delta) is detected Then the pipeline refreshes data within 60 minutes And increments the dataset version and retains the previous 90 days of versions for rollback And only changed records are updated; unaffected cache entries remain valid And a changelog is produced listing affected zones/jurisdictions and effective dates
Low-Latency 18-Month Calendar Service API
Given a client requests transitions and holidays for a scope (org/team/user) within the next 18 months When the service is queried Then it returns complete results including required metadata (jurisdiction, observance rules, partial_day flags, source, version) And respects applicable overrides before responding And meets performance SLOs: P95 latency ≤ 150 ms cached and ≤ 600 ms cold; availability ≥ 99.9% And responses include ETag/Last-Modified for cacheability and a dataset version identifier And filters by timezone, jurisdiction, and scope are supported and accurate
Org/Team/User Overrides and Deterministic Precedence
Given authorized actors define overrides (add/remove/modify holiday observance or mark half-day) at org, team, or user level When overrides conflict Then precedence is Org > Team > User and the winning rule is applied deterministically And each override stores scope, reason, creator, effective interval, and audit/version metadata And users may add personal observances without removing org-mandated holidays And the service reflects saved overrides in queries within 5 minutes
Provider Failover, Backfill, and Integrity Guardrails
Given a holiday provider or ICS feed is unavailable or returns errors When the aggregation process runs Then failover to secondary providers maintains 18-month coverage with no gaps for supported jurisdictions And the system retries with bounded exponential backoff and alerts on sustained failures And backfill jobs populate missing records within 24 hours after provider recovery And integrity checks quarantine anomalous records (invalid dates, duplicate IDs, timezone mismatches) and prevent them from reaching the API
Change Events Emission and Re-Forecasting Trigger
Given data changes impact any user's transitions or holidays When a new dataset version is published Then an event is emitted per impacted scope with payload: scope IDs, affected date ranges, change types, and dataset version And events are idempotent (deduplicated by content hash) and delivered at-least-once within 5 minutes of publish And subscribing services trigger re-forecast of impacted meeting series within 10 minutes of event receipt
After-hours Impact Forecast Engine
"As a team lead, I want to see which upcoming meetings will shift into after-hours due to DST or holidays so that I can address issues before they affect my team."
Description

Simulate each recurring meeting series across attendees’ time zones and configured work windows for the next planning horizon (e.g., 3–6 months), incorporating DST transitions and regional holidays to detect occurrences that become after-hours or violate equity/fairness constraints. Compute per-occurrence impact metrics (affected attendees, minutes outside window, severity score), produce a risk summary per series, and flag the earliest notice date for action. Run incrementally on data changes and on scheduled windows (30/14/7 days), persist idempotent forecast snapshots, and expose APIs/events to power UI alerts and automated workflows. Integrate with TimeTether’s fairness rotation to ensure forecasts reflect rotation commitments and upcoming turn-taking.

Acceptance Criteria
DST and Holiday Impact Detection Across Planning Horizon
Given a weekly meeting with attendees in America/Los_Angeles (09:00–17:00 local) and Europe/Berlin (09:00–17:00 local) scheduled at 09:00 Los Angeles time When the forecast runs for the next 6 months encompassing US and EU DST transitions Then any occurrence that pushes either attendee outside their configured work window due to DST is flagged as after-hours And occurrences landing on a configured regional holiday for any attendee are flagged as holiday-conflicted And each flagged occurrence stores the scheduled local time and the computed local time per attendee post-shift And unimpacted occurrences are not flagged
Per-Occurrence Impact Metrics Calculation
Given a flagged occurrence Then the engine computes and stores per-attendee after-hours minutes, total affected attendee count, and a severity score in the range 0–100 And the severity score mapping is: 0 = none; 1–39 = minor (<15 minutes outside window across ≤25% of attendees); 40–69 = moderate (15–59 minutes or 26–50% of attendees); 70–100 = major (≥60 minutes or >50% of attendees) And minutes outside window are rounded to the nearest minute And metrics are bit-for-bit identical across repeated runs with identical inputs
Risk Summary and Earliest Notice Date Generation
Given a meeting series with N future occurrences When the forecast completes Then a risk summary is produced containing total flagged occurrences, earliest flagged date, highest severity, and high-risk week buckets And the earliest notice date for each flagged occurrence equals the earliest of the scheduled checkpoints (30, 14, 7 days before the occurrence) that is still in the future at computation time And if a checkpoint has passed, the earliest notice date is set to the next upcoming checkpoint And the risk summary includes references to affected occurrence IDs
Incremental and Scheduled Forecast Execution
Given a scheduled run When the time window hits 30, 14, or 7 days before upcoming occurrences Then only series with occurrences in the respective window are re-evaluated Given a data change (attendee added/removed, timezone change, work window update, holiday calendar update, fairness rotation update, meeting time change) When the engine receives the change event Then only affected series are re-forecast within 60 seconds And each run records start/end timestamps and run reason (scheduled-30d, scheduled-14d, scheduled-7d, data-change) And throughput target: re-forecast at least 10,000 series within 5 minutes during scheduled runs
Idempotent Forecast Snapshot Persistence
Given no underlying inputs change between runs When the forecast executes for a series Then the same snapshotId is returned and no new persistent record is created Given underlying inputs have changed When re-forecasting the series Then a new snapshot with an incremented version is persisted and the prior snapshot remains retrievable And each snapshot includes a cryptographic content hash of inputs and outputs used for deduplication And concurrent runs for the same inputs result in at most one snapshot (deduped by content hash)
APIs and Events for Alerts and Automations
Given an authenticated client with read scope When it GETs /v1/forecasts/series/{seriesId} Then the API returns 200 with the latest snapshot including per-occurrence metrics and flags Given the forecast produces a new or changed risk for a series When persistence succeeds Then an event forecast.risk.updated is published within 30 seconds containing seriesId, snapshotId, diff summary, and earliestNoticeDate And the API returns 404 for unknown seriesId and 401/403 for unauthorized access And the API supports at least 100 requests per minute per client; events are retried with exponential backoff for up to 24 hours
Fairness Rotation Integration and Equity Checks
Given a fairness rotation that alternates after-hours burden monthly and a policy maxAfterHoursPerPerson = 60 minutes per rolling 4 weeks When forecasting the next 3 months Then the engine attributes predicted after-hours minutes to the correct person per rotation schedule And a fairness violation is flagged when any person’s forecasted cumulative after-hours minutes exceed 60 in any rolling 4-week window And occurrences that will be swapped by an upcoming scheduled rotation adjustment are forecast using the post-rotation assignment where applicable And equity metrics per series include per-person cumulative after-hours minutes and next rotation turn date
Preemptive Reschedule Suggestions
"As a meeting organizer, I want pre-vetted alternative time slots when DST or holidays would push a meeting after-hours so that I can quickly pick an equitable adjustment."
Description

Generate ranked adjustment options for impacted occurrences that keep all attendees within their work windows, respect regional holidays, and preserve TimeTether’s fairness-driven rotation. Offer strategies including: shift a single occurrence, shift a contiguous DST window range, or temporarily adjust the series cadence until the next transition. For each option, provide rationale, expected attendee impact, fairness effects, and conflicts avoided. Enforce minimum-notice policies, avoid blackout dates, and adhere to organizer/team constraints. Expose APIs and UI hooks so organizers can quickly choose and apply the best option.

Acceptance Criteria
Preemptive Suggestions Generated for Upcoming DST Shift
Given a recurring meeting series with attendees across multiple time zones and an upcoming DST transition within the org-configured detection window And a minimum-notice policy of N hours is configured When the DST Shield engine evaluates the series Then it produces a ranked list of suggestions containing at least one option for each strategy: single-occurrence shift, contiguous DST window shift, temporary cadence adjustment until next transition And all suggested times keep every attendee within their configured work-window bounds And no suggestion proposes changes to occurrences that start within N hours from now And suggestions are generated at least N hours before the first impacted occurrence And the list is sorted by descending total score computed from after-hours minutes avoided (primary), fairness preservation (secondary), and number of changes minimized (tertiary)
Fairness Rotation Preserved Within Tolerance
Given a fairness-driven rotation policy with a configured fairness_tolerance percent And a baseline fairness score for the series prior to adjustment When suggestions are generated Then each option includes fairness_effects metrics: before_score, after_score, delta, and per-attendee burden deltas And no option exceeds the configured fairness_tolerance on rotation equity And options that exceed fairness_tolerance are labeled "non-compliant" and ranked below all compliant options
Holiday and Blackout Compliance for Suggested Adjustments
Given regional holiday calendars for each attendee and team blackout dates and organizer constraints When suggestions are generated Then no suggested occurrence time falls on an attendee's regional public holiday or within a team blackout date window And no suggestion violates organizer- or team-level constraints And each option includes a conflicts_avoided list enumerating specific DST collisions, holidays, and blackout conflicts prevented
Option Payload Completeness and Rationale
Given the options API or UI data feed is requested for an impacted series When the response is returned Then each option includes: id, strategy, affected_occurrences (count and dates), proposed_times, rationale (plain-language), expected_attendee_impact (counts and minute deltas in/out of work window), fairness_effects (before/after/delta), conflicts_avoided, policy_compliance (min_notice, holidays, blackouts, constraints), and ranking_score (components) And all numeric metrics are non-negative and consistent with proposed changes And rationale is present and non-empty for every option
Organizer Quick Apply via UI and API
Given an organizer selects a suggestion in the UI or via POST to the Apply Suggestion API When the system applies the suggestion Then all affected occurrences are updated to the proposed times And calendar updates are dispatched to attendees with a one-click confirmation path where supported And the series metadata records the chosen strategy and any restoration point if temporary cadence is used And success is returned within 2 seconds P95 for series with up to 20 attendees and 12 impacted occurrences And audit logs capture who applied, when, which option id, and before/after times
Strategy Coverage: Single, Contiguous Window, Temporary Cadence
Given an upcoming DST transition impacts multiple consecutive occurrences When generating suggestions Then at least one option shifts only the next impacted single occurrence within policy constraints And at least one option shifts all impacted occurrences within the contiguous DST window range And at least one option temporarily adjusts the series cadence until the next transition and then restores the prior cadence automatically And each option identifies its coverage window and restoration behavior in the payload
One-click Updates & Attendee Notifications
"As an attendee, I want clear notifications with one-click options to accept a rescheduled meeting so that I don’t have to manually manage calendar changes."
Description

Deliver proactive alerts via email and Slack with clear context about the DST/holiday driver, local-time previews for each attendee, and one-click actions to accept or request changes. On acceptance, automatically update the calendar event via Google/Outlook integrations, send revised invites/ICS attachments, and preserve existing links and notes. Handle partial responses with organizer fallback, collect RSVPs, and post-confirmation summaries. Ensure localized messaging, accessibility, and rate limiting to avoid notification fatigue.

Acceptance Criteria
Proactive DST/Holiday Alert Content (Email & Slack)
Given a scheduled meeting occurrence is impacted by an upcoming DST shift or regional holiday for any attendee When TimeTether generates the proactive alert Then both email and Slack messages include the driver type (DST or Holiday) and impacted region(s) And include the original scheduled time and the proposed adjusted time And include each attendee’s local-time preview with timezone abbreviation And include one-click actions: Accept and Request Changes And include a link to view details in TimeTether And Slack uses interactive buttons; email includes actionable links And messages render correctly on mobile and desktop clients
One-Click Accept -> Calendar Update (Google & Outlook)
Given a recipient clicks Accept from email or Slack When the action is processed Then the calendar event is updated via Google Calendar and Microsoft Outlook 365 APIs to the proposed adjusted time And the event ID, conferencing link(s), and description/notes remain unchanged And the attendee list is preserved And updated invites and ICS attachments are sent to all attendees And a confirmation is posted in the originating Slack thread (if any) And repeated Accept actions within 10 minutes are idempotently ignored (no duplicate updates or notifications)
One-Click Request Changes -> Options and Organizer Notification
Given a recipient clicks Request Changes When the action is processed Then the recipient is presented with at least three suggested alternative times that avoid after-hours for all current attendees And the recipient can propose a custom time within their work window And the system records a Requested Change RSVP for that attendee And the organizer receives a notification with a summary of responses and top alternatives, including a one-click confirm action
Partial Responses With Organizer Fallback and RSVP Collection
Given multiple attendees respond with a mix of Accept and Request Changes When responses are received Then individual RSVPs (Accepted / Requested Change / No Response) are recorded and visible to the organizer And no calendar update is pushed if any required attendee requested changes, unless the organizer explicitly confirms a time And if all required attendees accept, the calendar update is executed automatically And non-responders remain invited and receive a single reminder after 24 hours, respecting rate limits
Localized Messaging and Accessibility
Given recipients have stored locale and timezone preferences When alerts and summaries are generated Then each recipient sees messaging localized to their preferred language (fallback to English if unsupported) And date/times are formatted per the recipient’s locale with explicit timezone labels And email content meets WCAG 2.1 AA (semantic headings, alt text for images, accessible button labels, sufficient contrast) And Slack messages include plain-text fallbacks and are compatible with screen readers
Rate Limiting, Deduplication, and DND Respect
Given multiple alerts could be sent to the same user in a short period When notifications are queued for delivery Then no more than 3 proactive DST/Holiday alerts are delivered per user in any rolling 24-hour period And alerts about the same meeting occurrence are deduplicated within a 10-minute window and threaded in Slack And notifications honor per-user Do Not Disturb windows (from Slack and user preferences) by deferring delivery until DND ends
Post-Confirmation Summary Delivery
Given a final decision is made to keep or adjust the meeting time When the decision is confirmed (by organizer or via all required attendee accepts) Then a confirmation summary is delivered via email and Slack to all attendees within 5 minutes And the summary includes the final time in each attendee’s local time, consolidated RSVP list, and a link to the calendar event And the updated ICS attachment is included and existing conferencing links and notes remain unchanged apart from time/date
DST Policy & Preferences Center
"As an org admin, I want to define how our company handles DST and holidays in scheduling so that adjustments follow our policies without manual oversight."
Description

Provide admin and user-level controls that govern DST/holiday behavior: auto-adjust vs approval-required, minimum notice windows, allowed shift ranges, series vs occurrence adjustments, observance rules (skip vs shift), work window definitions, regional holiday calendars per user, fairness policy rigidity, and blackout dates. Implement cascading policies (org → team → user) with transparent override explanations. Enforce these policies across forecasting, suggestion generation, and notification timing to ensure consistent, compliant behavior.

Acceptance Criteria
Policy Cascade Resolution & Override Explanation
Given an org-level policy: Auto-adjust, min notice 72h, allowed shift ±60m, observance Shift, fairness Strict And a team-level policy: Approval-required, min notice 48h And a user-level preference: Auto-adjust, min notice 24h When a DST or holiday impact is detected for a meeting owned by that team Then the effective policy is the team-level policy (Approval-required, min notice 48h) And the UI displays an override explanation showing, for each rule, the source level (Org/Team/User) and the effective value And forecasting, suggestion generation, and notification timing all use the effective values
Approval-Required Preemptive Adjustment Workflow
Given the policy requires approval for shifts >30m and the min notice is 72h And a DST change will shift the next occurrence by +60m into after-hours for at least one participant When the system detects the impact 10 days in advance Then it sends an approval request to the organizer immediately with options: Approve shift, Propose alternatives, Apply to series or this occurrence And if approved before T-72h, the event is rescheduled and one-click updated invites are sent within 1h of approval And if no response by T-72h, the policy fallback (Keep time or Skip) is executed and notifications are sent within 15m documenting the fallback
Minimum Notice Window Enforcement
Given a minimum notice window of 72h When a holiday or DST change is detected less than 72h before the affected occurrence Then the system does not auto-adjust the meeting time And it applies the configured observance rule to that occurrence (Skip or Keep time) And any alternative slot search is deferred to after the occurrence date And all notifications include the reason: "Inside minimum notice window"
Allowed Shift Range and Work Window Compliance
Given an allowed shift range of ±60m and participant work windows are defined per local timezone When generating preemptive suggestions for an affected occurrence Then every suggestion is within each participant’s local work window and within ±60m of the original start time And if no compliant time exists, only policy-compliant options are presented (e.g., Skip, Asynchronous update), and the conflict is flagged in the UI and event history
Regional Holiday Calendars and Observance Rules
Given User A uses US holidays (e.g., Jul 4) and User B uses FR holidays (e.g., Jul 14) And the observance rule is: Shift within same week; else Skip When forecasting a weekly series across June–July Then occurrences overlapping each user’s regional holidays are excluded from default suggestions And the system proposes alternatives within the same ISO week that meet work windows and allowed shift range And if none exist, the occurrence is marked Skip with a recorded rationale visible to organizer and invitees
Blackout Dates Enforcement
Given org-level blackout dates are set for 2025-11-24 through 2025-11-29 When generating suggestions, auto-adjusting, or sending invites for any affected series Then no meeting is scheduled or moved onto those blackout dates And if a computed shift would land in blackout, the system searches for the next policy-compliant slot outside blackout; else applies Skip per policy And notifications explicitly reference the blackout rule preventing scheduling
Fairness Rigidity Under DST and Holidays
Given fairness policy is Strict with maximum after-hours burden per user = 0 and rotation preserved When DST or holiday impacts would create uneven after-hours burden across the series Then the system rebalances by adjusting future occurrences within policy to preserve rotation and maintain zero after-hours for all users And user-level overrides that would increase any user’s after-hours burden are blocked with an explanation And all fairness-driven adjustments are logged with rationale in the series audit and reflected in forecasts and suggestions
Change History, Audit & Rollback
"As a program manager, I want an audit trail of DST-driven scheduling changes so that I can explain decisions and revert if needed."
Description

Record forecasts, suggestions, approvals, notifications, and calendar updates with timestamps, actors, rationale, and affected attendees. Provide UI and API access to view a chronological history, compare before/after local times, and roll back to a prior schedule when necessary. Support export for compliance, configurable retention, and PII minimization/anonymization aligned with org policies. Surface audit references in notifications for transparency.

Acceptance Criteria
End-to-End Audit Logging for DST Recalibration Flow
- Given a DST shift or regional holiday is detected for any attendee in a series protected by DST Shield, When the system generates a recalibrated forecast and preemptive adjustment suggestion, Then one audit event per action is persisted with schema: event_id (UUIDv4), series_id, meeting_id (nullable), event_type in [forecast_generated, suggestion_created], timestamp_utc (ISO 8601), actor_type in [system, user], actor_id (nullable), rationale_code, rationale_text, affected_attendee_count >= 1, before_local_times[] and after_local_times[] each including attendee_id (or hashed_id), iana_timezone, and utc_offset. - Given a user approves or rejects the suggestion, When the decision is recorded, Then an audit event is persisted with event_type in [approval_granted, approval_rejected] including approver_id and rationale. - Given notifications are dispatched to attendees, When messages are queued/sent, Then an audit event with event_type=notification_sent includes channel in [email, slack], recipient_count, and delivery_status in [queued, delivered, bounced]. - Given calendars are updated, When provider sync occurs, Then an audit event with event_type=calendar_update_applied includes provider in [Google, Microsoft, ICS], update_count, and sync_status in [success, partial, failed]. - Then all audit writes succeed within p95 <= 200 ms and are retried up to 3 times on transient errors; on permanent failure an event_type=error is recorded with correlation_id and error_code. - Then all related events in the flow share a non-empty correlation_id enabling traceability end-to-end.
History UI: Chronological View, Filters, Before/After Comparison
- Given a Scheduler Admin opens the History tab for a meeting series, When entries load, Then results are ordered reverse-chronologically, paginated at 50 per page (configurable up to 200), and display a total count. - Given filter controls, When the user filters by date range, event_type, actor, or attendee, Then the result set updates accordingly and the active filters are clearly shown and can be cleared individually. - Then each entry displays event_type, timestamp in both viewer local time and UTC, actor display name, rationale, and affected_attendee_count. - Then for time-changing events (forecast_generated, suggestion_created, approval_granted, calendar_update_applied, rollback_executed) a comparison view shows before vs after local time per affected attendee with iana_timezone and utc_offset; at least one attendee shows a change when a DST shift is involved. - Then first-page load latency is p95 <= 1.5 s for up to 500 total entries and p95 <= 2.5 s for up to 5,000 entries. - Then the UI meets WCAG 2.1 AA for contrast, focus order, and keyboard navigation; table supports column headers and screen-reader labels.
API Access & Compliance Export with Traceability
- Given an authorized client with scope audit:read calls GET /v1/audit-history with filters (series_id, meeting_id, actor_id, event_type[], attendee_id/hashed_id, from, to), When the request is valid, Then a 200 response returns items in reverse-chronological order with a stable schema_version and cursor-based pagination (limit <= 200, next_cursor); unauthorized clients receive 403. - Then response times are p95 <= 800 ms for pages of 50 items and p95 <= 1.2 s for pages of 200 items under 10k total records. - Given a client initiates an export via POST /v1/audit-exports specifying format in [CSV, NDJSON] and optional pgp_public_key, When accepted, Then an export job is created and returns export_id and status in [queued, running, complete, failed]. - When the export completes, Then downloadable artifacts include data file(s) and a manifest.json with schema_version, generated_at (UTC), row_count, sha256 checksums per file, and optional armored PGP encryption; export respects retention and PII policies. - Then datasets up to 10,000 rows complete export within p95 <= 2 minutes; larger exports stream chunked files of <= 100 MB each. - Then each export creates an audit event event_type=export_generated and sends a notification to the requester including the export_id and audit reference.
Rollback to Prior Schedule with Integrity and Notifications
- Given a Scheduler Admin selects a prior audit event that modified a series schedule, When they click Rollback and confirm, Then the system restores the series to the exact prior state atomically (all-or-nothing) using the selected snapshot. - Then the system sends replacement invites/updates and necessary cancellations to all impacted attendees, and calendar providers reflect the restored schedule. - Then a new audit event with event_type=rollback_executed is recorded including source_event_id, reason, actor_id, affected_attendee_count, and correlation_id linking to the original change. - Then the operation is idempotent: repeating rollback to the same target produces no additional changes and logs a no-op notice. - Then if hard conflicts are detected (outside attendee work windows or direct collisions), the rollback is blocked pre-commit, conflicts are listed to the user, and no partial changes are applied. - Then all rollback notifications include an Audit Reference ID and rationale and are tracked with delivery statuses in audit history.
Configurable Data Retention and Secure Purge
- Given an Org Admin sets retention_days within [30, 1825] (default 365), When saved, Then the policy is persisted, auditable, and visible in UI and via GET /v1/org-settings. - Then a scheduled job at 02:00 UTC daily deletes audit records older than now() - retention_days where legal_hold=false. - Then changing retention_days to a shorter value triggers an immediate purge operation. - Then a purge_completed audit event is recorded containing cutoff_date, deleted_count, and duration_ms; purged records are irrecoverable. - Then subsequent UI/API queries return no records older than cutoff_date unless legal_hold=true. - Then export jobs exclude purged records and include a note of the active retention policy in manifest.json.
PII Minimization and Anonymization Policy Enforcement
- Given org policy anonymize_attendees_in_audit=true, When audit entries are stored or exported, Then attendee identifiers are rendered as hashed_id = SHA-256(lower(email_local_part) + org_salt) and domain is preserved; display names are omitted; UI shows a masked indicator. - Given anonymize_attendees_in_audit=false and the viewer role is in [Org Admin, Scheduler Admin], Then full attendee names and emails are displayed; for other roles, emails are masked as a***@domain and names are initials-only. - Then rationale fields are normalized and scrubbed to remove email addresses and phone numbers in both UI and exports regardless of policy. - Then API/export endpoints enforce masking based on viewer token and org policy; attempts to bypass masking return 403 and are logged with event_type=error. - Then hashing and masking rules are deterministic per org and verifiable across UI and export outputs for the same event.
Notifications Include Audit References and Rationale
- For every DST Shield suggestion, approval, calendar update, or rollback notification sent via Email or Slack, the message body includes: a human-readable reason (e.g., DST/holiday impact), a unique Audit Reference ID (pattern AH-[A-Z0-9]{8}), and a deep link to the Audit History view (auth required). - Then no PII beyond the recipient’s own identity appears in notifications to external recipients; links carry short-lived tokens expiring in 7 days. - Then a corresponding audit event event_type=notification_sent is recorded with channel, recipient_count, delivery_status in [queued, delivered, bounced], and audit_reference_id. - Then sample notifications for both DST shift and regional holiday scenarios render the correct before/after local time summary and working-window impact note.

What‑If Sandbox

An interactive planner to simulate shifting times, cadences, or attendees and instantly see the Fatigue Radar delta and fairness score. Compare scenarios, pick the least‑fatiguing plan, and push decisions live in a click.

Requirements

Interactive Scenario Builder
"As a distributed team lead, I want to quickly tweak meeting times, cadence, and attendees in a sandbox so that I can explore options without disrupting our live schedule."
Description

Provide an interactive planner where schedulers can adjust meeting times, cadence, and attendee lists and instantly preview effects across time zones. Enable drag-and-drop time blocks that snap to each attendee’s work window, support recurring patterns and rotation settings, and seed a scenario from the current live schedule. Pull real-time availability (calendars, PTO, regional holidays) from connected providers, display conflicts inline, and allow quick add/remove/replace of attendees. Ensure smooth performance with immediate UI feedback and keyboard-accessible controls to support fast iteration.

Acceptance Criteria
Seed From Live Schedule Into Sandbox
Given the user has connected calendar providers and an active live schedule When the user selects 'Seed from Live' in Scenario Builder Then the sandbox populates meetings, attendees, rotation settings, and work windows within 2 seconds And a 'Synced from Live' timestamp is displayed And any provider sync failure is shown as a non-blocking warning while cached data is used
Drag-and-Drop With Work-Window Snapping
Given a meeting block is dragged on the timeline When the drop occurs outside one or more attendees' work windows Then the block snaps to the nearest slot that fits the intersection of selected attendees' work windows And if no intersection exists on that day, an inline 'No common work window' conflict is shown and the drop is reverted And snap guidance (ghost outline and tooltip) appears within 100 ms during drag
Attendee Changes Update Availability & Conflicts
Given connected calendars, PTO feeds, and regional holiday data When the scheduler adds, removes, or replaces attendees via the attendee control Then availability and conflict badges recalculate within 1 second And conflict badges list the attendee(s), conflict type (busy, PTO, holiday, outside work window), and first conflicting instance And typeahead search returns results within 300 ms and selection via Enter adds the attendee And replacing an attendee preserves the role assigned to that seat
Recurrence Patterns and Rotation Settings
Given a meeting has a weekly, biweekly, or monthly cadence with rotation enabled When the user changes cadence or rotation rules Then the preview regenerates the next 12 occurrences within 1 second And rotation assignments alternate per rule without assigning more than one after-hours occurrence to the same attendee within any 4-week window when a feasible alternative exists And exceptions caused by PTO or holidays are shifted to the nearest feasible slot and marked as exceptions
Instant Fatigue Radar and Fairness Score Preview
Given a seeded baseline and an edited scenario When the user adjusts time, cadence, or attendees Then Fatigue Radar delta and fairness score update within 200 ms of the change And per-attendee after-hours minutes and meeting count deltas are displayed And a red risk indicator appears if the fairness score degrades by more than 5 points from baseline
Keyboard Accessibility and Nudge Controls
Given the user navigates with keyboard only When focus is on a meeting block Then the user can nudge start time by 15-minute increments with Alt+Arrow keys and adjust duration with Shift+Arrow keys And Enter opens the meeting details and Escape closes without changes And every interactive control is reachable via Tab with visible focus and correct ARIA roles and labels
Smooth Performance Under Load
Given a scenario with 50 attendees across 6 time zones and 5 recurring meetings each When the user drags a block, edits cadence, or changes attendees Then the interface maintains at least 55 FPS during interaction on the baseline test device (11th-gen Intel i5, 8 GB RAM, latest Chrome) And all recomputations complete within 2 seconds, showing a progress indicator if longer than 300 ms And no single action blocks the UI thread for more than 50 ms
Real-time Fatigue & Fairness Delta Engine
"As a scheduler, I want to see the immediate change in fatigue and fairness metrics as I adjust a scenario so that I can pick the least burdensome plan."
Description

Compute and display the delta between a sandbox scenario and the baseline for Fatigue Radar and fairness score in real time. Show per-attendee and team-level impacts such as after-hours minutes, meeting load distribution, and rotation balance, with color-coded indicators and tooltips. Use TimeTether’s existing scoring models, updating within 300 ms for teams up to 50 attendees, and degrade gracefully with loading states for larger cohorts. Expose a lightweight API for the UI to request recalculations on every change.

Acceptance Criteria
Real-time Delta Calculation ≤50 Attendees
Given a baseline plan and a sandbox scenario with up to 50 attendees loaded When the user changes any parameter (time, cadence, or attendee list) Then a recalculation request is sent immediately for that change And all visible delta values (team and per-attendee) update in the UI within 300 ms for the 95th percentile over 100 consecutive changes And the order of changes is preserved such that the latest change’s result is what is shown (last-write-wins) And no change events are dropped (each distinct change results in one corresponding recalculation request)
Per-Attendee and Team-Level Delta Coverage
Given a delta computation is triggered When the results are returned Then the payload and UI include team-level fairnessScoreDelta and Fatigue Radar deltas for each dimension and the composite score And for each attendee include afterHoursMinutesDelta, meetingCountDelta, and rotationBalanceDelta And attendees with no impact show 0 deltas and a neutral indicator And team-level aggregates are internally consistent (e.g., sum of meetingCountDelta across attendees equals the team meeting load delta)
Color-Coded Indicators and Legend
Given computed deltas are available When indicators are rendered Then metrics where lower is better (e.g., after-hours minutes, fatigue load) use: negative delta = green, positive delta = red, zero = gray And metrics where higher is better (e.g., fairness score, rotation balance) use: positive delta = green, negative delta = red, zero = gray And a visible legend explains the color mapping And color is not the only cue (up/down/flat icons are shown) and meets WCAG 2.1 AA contrast
Accessible Tooltips with Baseline vs Scenario Details
Given a user hovers, focuses, or taps on any delta indicator When the tooltip is invoked Then it appears within 100 ms and displays: metric name, baseline value, scenario value, absolute delta, and percentage delta And the tooltip text references the TimeTether scoring model used And tooltips are keyboard and screen-reader accessible (focusable trigger, ESC to dismiss, aria-describedby set)
Graceful Degradation for Large Cohorts
Given a sandbox scenario exceeding 50 attendees or an operation predicted to exceed 300 ms When a change is made Then a visible loading state (skeleton/spinner with “Calculating…”) appears within 150 ms and persists until fresh deltas render And dependent actions (e.g., apply decision) remain disabled until results are ready And if multiple changes occur during loading, only the latest result is rendered (no flicker from outdated responses)
Lightweight Recalculation API Contract
Given a client submits a recalculation request with baselineId and scenario payload When POSTing to the recalculation endpoint Then the server responds 200 with a JSON body containing team and per-attendee deltas (fairnessScoreDelta, fatigueRadar.deltas[], afterHoursMinutesDelta, meetingCountDelta, rotationBalanceDelta) and echoes a client-supplied requestId And p95 server response time is ≤ 200 ms for scenarios with ≤ 50 attendees, enabling ≤ 300 ms end-to-end UI updates And the API accepts at least 10 requests per second per client without rate limiting and returns 400 with error details on invalid payloads
Model Parity and Accuracy
Given the same baseline and scenario are evaluated by both the delta engine and the existing scoring models When results are compared Then fairness score deltas match within ±0.01, fatigue radar dimension deltas within ±0.5 units, after-hours minutes within ±1 minute, and rotation balance within ±0.01 And rounding rules in the UI match back-end rounding so displayed values are consistent across views
Multi-Scenario Comparison & Ranking
"As a program manager, I want to compare several scheduling options side by side so that I can confidently choose the best trade-off for the team."
Description

Allow users to create, name, and store multiple sandbox scenarios, then compare them side by side with summarized metric deltas and a composite ranking. Present sortable lists and quick-glance visuals for fatigue and fairness, highlight top candidates, and support tagging (e.g., "Asia-friendly"). Provide tie-breaker rules (fewest after-hours minutes, highest fairness, least total changes) and enable one-click selection of a winner.

Acceptance Criteria
Create, Name, and Store Multiple Scenarios
Given the user is in the What-If Sandbox with an active team When they create at least five scenarios and assign unique names Then all scenarios appear in the scenario list, persist after page reload, and are available for comparison Given a scenario is selected When the user renames the scenario and saves Then the new name is shown consistently across the list, compare view, and ranking, and persists after reload Given the user attempts to save a scenario without a name When they submit Then a validation error prevents saving and no unnamed scenario is created
Side-by-Side Comparison With Metric Deltas
Given a baseline is set (default is the current live plan) When the user selects 2 to 5 scenarios to compare Then a comparison table displays per scenario: Scenario Name, Fatigue Radar delta (±), Fairness Score, After-hours minutes, and Total changes count Given the comparison table is visible When the user edits any scenario’s time, cadence, or attendees Then the metrics and deltas recalculate and render within 2 seconds Given metric deltas are shown When a delta represents an improvement vs baseline Then it shows a leading minus sign for reductions (e.g., minutes), a green indicator, and a tooltip explaining the delta direction
Composite Ranking With Specified Tie-Breakers
Given two or more scenarios exist in the sandbox When the system computes rankings Then each scenario displays a rank position and the list is ordered deterministically Given two or more scenarios have equal composite scores When the system orders them Then ties are resolved in this priority: (1) fewest after-hours minutes, (2) highest fairness score, (3) least total changes; if still tied, order is stable by creation timestamp Given rankings are displayed When any metric or baseline changes Then ranks update within 2 seconds and the top-ranked scenario is visually indicated
Sorting and Quick-Glance Visuals
Given the comparison list is visible When the user clicks a column header for Rank, Fatigue Radar delta, Fairness Score, After-hours minutes, or Total changes Then the list sorts by that column and toggles ascending/descending on subsequent clicks, preserving stable order for ties Given scenarios are listed When the list renders Then each scenario shows quick-glance visuals: a fatigue badge (traffic-light color) and a fairness badge, with tooltips that define the metric and units Given the user sets a sort order When they navigate away and return within the same session Then the last-used sort column and direction are preserved
Scenario Tagging and Filtering
Given a scenario exists When the user adds tags (e.g., "Asia-friendly") to the scenario Then up to five tags can be attached, tags display as chips in the list, and persist after reload Given tags exist across scenarios When the user filters by a tag from the list header or search Then only scenarios containing that tag are shown, and the active filter is clearly indicated with a count of results Given a tag is removed from a scenario When the user deletes the tag Then it no longer appears on that scenario and filtered views update immediately
One-Click Winner Selection and Live Push
Given a ranked list of scenarios is visible When the user clicks "Select Winner" on a scenario and confirms Then the selected scenario is applied to the live schedule, the scenario is marked with a Live badge, and success feedback is shown within 60 seconds Given a winner has been pushed live When the user opens the sandbox Then the live scenario is identifiable as the baseline and cannot be deleted while live Given the user selects a winner When the action completes Then an audit entry is recorded with timestamp, actor, and scenario name
Baseline Selection and Delta Accuracy
Given the sandbox is open When no baseline is explicitly chosen Then the current live plan is used as the default baseline Given multiple scenarios exist When the user sets any scenario as the baseline from the compare view Then all delta values recompute relative to that baseline within 2 seconds Given deltas are displayed When comparing any scenario S against baseline B Then after-hours delta = S_after_hours − B_after_hours, fairness delta = S_fairness − B_fairness, and total changes delta = S_changes − B_changes, with signs and units shown correctly
One-Click Apply to Live Schedule
"As a meeting owner, I want to push the chosen scenario live in one click so that I can implement decisions without manual rework."
Description

Enable a selected scenario to be promoted to production with a single confirmation. Generate or update recurring events, apply fairness-driven rotations, and sync to connected calendars while preserving attendee-specific work windows. Present a change summary (added/removed attendees, time shifts, series updates), and send one-click invites or updates with personalized notifications. Record an auditable change log and provide an immediate rollback option if issues arise.

Acceptance Criteria
Promote Scenario and Sync Calendars
Given a user selects a What‑If Sandbox scenario and has active calendar connections with required scopes, When the user clicks “Apply to Live” and confirms, Then the system creates or updates all recurring events defined by the scenario across connected calendars, applies the fairness rotation, preserves attendee-specific work windows and time zones, and completes synchronization within 60 seconds for up to 50 attendees and 10 series; And all created/updated events are associated with deterministic external IDs that include the scenario ID; And no occurrence is scheduled outside any attendee’s declared work window.
Present Change Summary Before Apply
Given a user clicks “Apply to Live” for a selected scenario, When the confirmation dialog is displayed, Then it shows a change summary including: number of series impacted, number of occurrences changed, attendees added/removed by series, time shifts (before→after) per attendee in their local time, recurrence rule changes, and the fairness score and Fatigue Radar delta; And no calendar changes are made until the user confirms; And the summary includes a downloadable diff (CSV/JSON) with unique event identifiers.
Send Personalized Invites/Updates
Given an apply operation succeeds, When invitations/updates are dispatched, Then each attendee receives a single invite/update within 2 minutes containing their local-time start/end, change highlights (added/removed/shifted), and recurrence summary; And updates reuse the existing event UID so responses thread correctly; And attendees not included in the new plan receive a cancellation with reason; And delivery failures are logged and surfaced to the actor with a retry option.
Record Immutable Audit Log
Given an apply operation completes (success or rollback), When the system writes the audit entry, Then it stores an immutable record containing: actor user ID, team/project ID, timestamp (UTC), scenario ID/name, scenario version hash, fairness score and Fatigue Radar delta (before/after), per-event diff (created/updated/deleted with external IDs), connected calendar account IDs, API request IDs, and a rollback token; And the audit entry is queryable via the audit API within 10 seconds.
One‑Click Rollback to Previous State
Given the most recent apply occurred within the last 15 minutes, When the user clicks “Rollback” and confirms, Then the system restores the prior calendar state across all impacted series: recreates deleted events with original UIDs, removes newly created events, and reverts updated events to prior fields; And personalized rollback notifications are sent within 2 minutes; And the rollback completes within 90 seconds and is idempotent (repeated rollbacks make no additional changes).
Transactional Apply with Failure Handling
Given any step of the calendar write operations may fail, When applying changes, Then the apply is atomic at the scenario level: if any create/update/delete fails, all changes are rolled back and the calendar state remains unchanged; And the user is shown an error with a list of failed accounts/events and diagnostic codes; And no orphaned or duplicate events remain in any connected calendar; And a retry can be initiated after remediation without duplicating events (via an idempotency key).
Enforce Fairness Rotation and Work Windows
Given fairness rotation rules and attendee-specific work windows are configured, When the user applies the scenario, Then the resulting series reflect the specified rotation pattern and the post-apply fairness score displayed matches the computed score within 0.1; And no occurrence violates any attendee’s work window; And if a violation would occur, the apply is blocked with a pre-flight validation report listing offending occurrences and attendees, with counts by series.
Constraint & Policy Guardrails
"As an admin, I want the sandbox to enforce our meeting policies and suggest compliant alternatives so that schedules remain fair and policy-adherent."
Description

Validate sandbox edits against organizational policies and individual constraints, including hard work hours, quiet times, maximum weekly meeting counts, mandatory attendee requirements, and regional holidays. Surface violations in real time with clear, actionable messages and suggest the nearest compliant alternatives. Allow admins to define guardrails and optionally override with a required reason that is logged for compliance.

Acceptance Criteria
Real-Time Guardrail Validation in Sandbox
Given a user is editing a scenario in the What‑If Sandbox When the user changes time, cadence, duration, or attendees Then guardrail validation updates visible results within 300 ms And all applicable violations are listed with policy name, severity (Warning or Blocking), impacted attendees, and localized timestamps And the fields causing violations are highlighted inline And the Push Live action is disabled if any Blocking violations exist and enabled if only Warnings or none exist And reverting the change clears resolved violations within 300 ms
Enforce Work Hours and Quiet Times
Given each attendee has configured work hours and quiet times in their local timezone When a proposed meeting occurrence falls outside an attendee’s work hours or inside their quiet time Then a Blocking violation is raised for that attendee showing the local start/end and the breached window And the system proposes at least 3 nearest compliant slots within the next 14 days, computed within 2 seconds And selecting a proposed slot updates the scenario and removes the specific violation if compliant
Maximum Weekly Meeting Count Enforcement
Given an attendee has a maximum weekly meeting count policy When the proposed scenario would increase that attendee’s meetings above the configured limit for any ISO week Then a Blocking violation is raised showing current count, proposed count, and the limit And the system suggests compliant cadence adjustments or time shifts that keep counts within the limit And updating attendees in the scenario recalculates weekly counts and validations within 300 ms
Mandatory Attendee Requirement
Given one or more attendees are marked mandatory for the meeting When the user attempts to remove a mandatory attendee or select a time they cannot attend due to constraints Then a Blocking violation prevents Push Live and identifies the mandatory attendee(s) And the system proposes compliant times that include all mandatory attendees And only Admin users see an Override option for Blocking violations
Regional Holiday Compliance
Given each attendee’s locale and holiday calendar are known When a proposed occurrence lands on a regional public holiday for any required attendee Then a violation is raised per policy severity (Blocking or Warning) and displays the holiday name and impacted locale And compliant suggestions exclude holidays and observed days for impacted attendees And the Fatigue Radar delta and fairness score are recalculated for any suggested alternative
Nearest Compliant Alternatives and Impact Metrics
Given there are one or more violations in the current scenario When the user opens View Alternatives Then the system returns 3–10 alternative schedules that resolve all current Blocking violations within 2 seconds And alternatives are ranked to minimize Fatigue Radar delta and keep the fairness score within ±2 points of baseline And each alternative displays which violations it resolves and which warnings remain, if any And applying an alternative updates the scenario and clears resolved violations immediately
Admin Guardrails: Configuration, Override, and Audit
Given an Admin is managing guardrails When the Admin creates or edits a guardrail policy with scope (Org, Team, Individual) and severity (Warning or Blocking) Then the policy is validated, versioned, and becomes effective for Sandbox validation within 60 seconds And changes are recorded with actor, timestamp, previous and new values, and scope in an immutable audit log When an Admin overrides a Blocking violation to Push Live Then a non-empty reason of at least 10 characters is required And the override, reason, actor, timestamp, affected policy IDs, attendees, and old/new values are logged and queryable And non-admin users cannot perform overrides and see an authorization error
Scenario Save, Share, and Permissions
"As a team lead, I want to save and share proposed schedules for feedback so that stakeholders can review and refine plans before we go live."
Description

Allow users to save scenarios with names and notes, share them with specific teammates or groups, and set permissions for view or edit. Provide comment threads and mentions to gather feedback before applying changes. Support secure share links with expiration, respect organization SSO and role-based permissions, and retain a version history of edits and decisions.

Acceptance Criteria
Save Scenario with Name and Notes
Given I have modified planning parameters in the What‑If Sandbox When I click “Save Scenario” and provide a unique name (required) and notes (optional) Then the scenario is created, I am set as Owner, and a success confirmation appears And the scenario is listed in my Scenarios list and accessible via a stable URL within 2 seconds Given another scenario in the same workspace has the same name When I attempt to save Then I am prompted to choose a different name or explicitly Update Existing; no silent overwrite occurs When a save fails due to validation or network error Then an error message explains the cause and no partial scenario is created
Share Scenario with Specific Teammates and Groups
Given I am the scenario Owner or have Edit permission When I open Share settings and search by user or group name/email Then matching users/groups return within 500 ms for typical queries When I grant View or Edit access to selected recipients Then recipients appear in the access list with their role and source (direct or group) And recipients receive an in‑app notification immediately and an email within 1 minute And duplicate entries are prevented When I remove a recipient or group Then their access is revoked within 15 seconds and they are removed from the access list
Permission Levels: View vs Edit Enforcement
Given a user has View access to a scenario When they open the scenario Then they can see names, notes, parameters, fatigue/fairness outputs, and version history And all editing controls (parameters, name/notes, sharing, version revert) are disabled And API attempts to modify the scenario return 403 Given a user has Edit access When they open the scenario Then they can modify name, notes, parameters, and sharing, and save changes And permission changes propagate to all clients within 15 seconds Given a user is the Owner When they choose to delete the scenario Then deletion requires explicit confirmation and is logged; non‑owners cannot delete
Comment Threads and @Mentions for Feedback
Given a scenario with at least one collaborator When any user with View or Edit access posts a comment or reply Then the comment appears in a threaded view with author, timestamp (UTC), and is visible to all with access When a user types @ and a name Then an autocomplete shows share‑eligible users/groups within 500 ms And selecting a person not yet shared prompts to add access or cancel mention When a user is mentioned Then they receive an in‑app notification immediately and an email within 1 minute containing a deep link When a comment is edited or deleted by its author within 15 minutes Then the change is reflected, and an audit marker (edited/deleted) is shown; all actions are recorded in the audit log When a thread is resolved by an Editor or Owner Then the thread collapses and its status is reflected in the unresolved count badge in real time (<2 seconds)
Secure Share Links with Expiration
Given I am the Owner or an Editor When I create a secure share link Then I can choose permission (View or Edit) and set an expiration date/time (UTC) And the generated URL has at least 128 bits of entropy and is only retrievable once at creation When a user opens the link Then they must authenticate and satisfy org SSO requirements before access is granted And access is constrained to the link’s permission level regardless of existing rights When the link expires or is revoked by the Owner/Editor Then subsequent requests return 410 (expired) or 403 (revoked) And revocation takes effect within 15 seconds across all clients
Respect Organization SSO and Role‑Based Access Control
Given the organization enforces SSO When a user attempts to access a shared scenario without an active SSO session Then access is denied and the user is redirected to SSO login Given RBAC roles Scenario.View and Scenario.Edit When a user lacks the required role for an action Then the action is blocked with a clear message and a 403 is returned by APIs, even if directly shared When a user is deactivated or removed from a group that granted access Then access is revoked within 5 minutes and logged with user, outcome, and reason code
Version History of Edits and Decisions
Given a scenario When any change is saved to parameters, name/notes, permissions, or comments resolution state Then an immutable version record is created with timestamp (UTC), actor, and summarized diffs When a user views Version History Then the list loads within 2 seconds for up to 100 versions and shows who, when, and what changed And selecting a version shows a field‑level diff including share/permission changes Given the user has Edit access When they revert to a prior version Then the current state is replaced with the selected version and a new version entry is created noting the revert When a version is marked as a Decision with an optional note Then it is visually labeled and filterable in the history view

Quorum Pick

Let the nudge close the loop automatically. Set a quorum (e.g., required attendees or 70% of invitees) and a decision deadline; once reached, TimeTether instantly locks the winning slot, cancels the conflicted occurrence, and sends updated invites. Tie‑breakers favor fairness and attendance impact, so coordinators don’t babysit threads and teams get clarity faster.

Requirements

Quorum Rule Configuration
"As a coordinator, I want to set quorum rules (percentage or required attendees) so that decisions can be made automatically when sufficient responses are received."
Description

Enable coordinators to define quorum criteria per meeting series or per occurrence, including percentage-based thresholds (e.g., 70% of invitees), absolute minimum counts, and named required attendees by person or role. Provide UI and API surfaces to set, preview, and validate rules against the current invitee list, with guardrails (e.g., required attendees cannot exceed total invitees, percentage and absolute thresholds must be achievable). Persist settings in series metadata and expose them to the scheduling engine so Quorum Pick can evaluate responses in real time. Enforce permissions so only authorized coordinators can set or modify quorum rules. This configuration reduces manual follow-up, guarantees key stakeholder inclusion, and integrates seamlessly with TimeTether’s attendee directory and roles.

Acceptance Criteria
Series-level percentage-based quorum configuration
Given a meeting series with 10 invitees and a coordinator with edit permission When the coordinator sets a percentage quorum of 70% and clicks Save Then the UI displays "Required responses: 7 of 10" prior to confirmation And the system saves quorumPercentage=70 and derivedRequiredCount=7 in series metadata And GET /series/{seriesId}/quorum-rules returns quorumPercentage=70 and derivedRequiredCount=7 And values outside 1–100% are blocked with inline error messaging and Save is disabled
Absolute minimum count quorum guardrails
Given a meeting series with 8 invitees When the coordinator sets absoluteMinimum=6 and saves Then the rule is accepted and persisted and GET reflects absoluteMinimum=6 When the coordinator sets absoluteMinimum=9 (> total invitees) Then Save is disabled and an inline error states "Absolute minimum must be between 1 and 8" And POST /series/{seriesId}/quorum-rules responds 422 with errorCode="ABSOLUTE_MIN_EXCEEDS_INVITEES"
Required attendees by person and role with directory integration
Given invitees include userA, userB, userC and role "Tech Lead" maps to userB in the directory When the coordinator adds requiredAttendees persons=[userA] roles=["Tech Lead"] and saves Then the UI previews the resolved set {userA, userB} before save And Save is blocked if any required attendee is not in the invitee list with errorCode="REQUIRED_NOT_INVITEE" And Save is blocked if count(requiredAttendeesResolved) > totalInvitees with errorCode="REQUIRED_EXCEEDS_INVITEES" And the persisted rule stores persons by ID and roles by roleKey, and exposes resolvedRequiredAttendees on GET
Per-occurrence quorum override and inheritance
Given a series with default quorum rules When the coordinator opens occurrence on a specific date and enables "Override quorum" to set different rules and saves Then that occurrence’s effective quorum rules equal the override while other occurrences inherit series defaults And selecting "Inherit from series" removes the override and re-applies series rules And GET /occurrences/{occurrenceId}/quorum-rules returns the effective rules and inheritance=true|false
Real-time preview and API validation of quorum achievability
Given draft quorum inputs (percentage, absolute minimum, required attendees) and a current invitee list When the coordinator modifies any input or the invitee list changes Then the UI recomputes derived counts and an Achievable/Unachievable indicator within 500 ms And POST /series/{seriesId}/quorum-rules:validate returns {isAchievable, computedRequiredCounts, errors[]} without persisting changes
Permissions enforcement for quorum configuration
Given a non-coordinator user views quorum settings Then fields are read-only and Save actions are disabled When a non-coordinator calls POST/PUT quorum rules endpoints Then the API responds 403 Forbidden with errorCode="INSUFFICIENT_PERMISSIONS" And for successful changes by authorized coordinators, an audit log entry is created with actorId, timestamp, and before/after diff
Persistence and scheduling engine propagation of quorum rules
Given quorum rules are updated for a series and saved successfully When the save completes Then the rules are durably persisted and published to the scheduling engine within 10 seconds And subsequent Quorum Pick evaluations for that series use the updated rules to compute quorum and locking behavior And decision telemetry records ruleVersion and ruleSource (series|occurrence) used in each evaluation
Decision Deadline & Auto-Lock
"As a coordinator, I want to define a decision deadline and have the meeting auto-lock when criteria are met so that I don’t have to babysit threads."
Description

Allow coordinators to set a decision deadline for each meeting occurrence or series. When quorum is met before or at the deadline, automatically lock the winning time slot and transition the meeting state to Locked. If quorum is not met by the deadline, follow a configurable fallback policy (e.g., pick the best attendance score, keep the existing slot, or extend the deadline once). Implement an event-driven evaluator that is timezone-aware, concurrency-safe, and idempotent to prevent double-locking under rapid response conditions. Emit a Lock Finalized event to trigger downstream actions (cancellations, updates, notifications). This removes manual babysitting, speeds clarity, and ensures consistent outcomes.

Acceptance Criteria
Auto-Lock on Quorum at or Before Deadline
Given a meeting occurrence with a configured decision deadline and a defined quorum rule And at least one candidate time slot with attendee responses When quorum is met for a specific slot any time before or exactly at the deadline Then the system selects that slot as the winner And transitions the occurrence state to Locked And records a lock_timestamp that is less than or equal to the deadline timestamp in UTC And emits exactly one Lock Finalized event with fields: meeting_id, occurrence_id, selected_slot_id, previous_state, new_state=Locked, lock_reason=quorum_met, deadline_ts, quorum_summary, idempotency_key, correlation_id, emitted_at
Fallback Policy Application When Quorum Missed
Given a meeting occurrence with fallback_policy=best_attendance_score and no slot meets quorum by the deadline When the deadline passes Then the system selects the slot with the highest attendance score using configured tie-breakers (fairness, attendance impact) And transitions the occurrence to Locked And emits one Lock Finalized event with lock_reason=fallback_best_score Given a meeting occurrence with fallback_policy=keep_existing_slot and no slot meets quorum by the deadline When the deadline passes Then the system retains the currently scheduled slot and locks it And emits one Lock Finalized event with lock_reason=fallback_keep_existing Given a meeting occurrence with fallback_policy=extend_once:P1D and no slot meets quorum by the deadline When the deadline passes Then the system extends the decision deadline by P1D exactly once and does not lock yet And marks extension_used=true on the occurrence And if quorum is still not met at the extended deadline, applies the next configured fallback and locks the occurrence, emitting Lock Finalized with lock_reason=fallback_after_extension
Concurrency-Safe and Idempotent Evaluator
Given multiple RSVP updates for the same occurrence arrive within 100ms of each other around the decision deadline And the evaluator is triggered concurrently by multiple workers or retries When the evaluator processes these events Then only a single state transition to Locked is committed (version/compare-and-swap succeeds once) And exactly one Lock Finalized event is published And any additional evaluator attempts detect the locked state and perform no-op without emitting duplicate events And all emitted/processed events share the same idempotency_key for deduplication
Timezone-Aware Deadline Evaluation Including DST Boundaries
Given the meeting timezone is set to a specific IANA zone (e.g., America/Los_Angeles) and a decision deadline defined in meeting local time When attendee responses are received from multiple timezones Then the system converts the deadline to UTC using the meeting timezone rules and evaluates responses against the UTC-normalized deadline And a response timestamp exactly equal to the deadline is treated as on-time (inclusive boundary) And responses after the deadline are excluded from quorum calculations And deadlines on DST transition days are interpreted using correct offset without skipping or duplicating the evaluation window
Series-Level Deadline with Occurrence Override
Given a recurring series with a series-level decision deadline And a single occurrence has an override deadline set When evaluating the overridden occurrence Then the override deadline is used instead of the series default And editing the series deadline updates all non-overridden future occurrences while preserving existing overrides And removing an override reverts that occurrence to the current series deadline And changing the series timezone recalculates all series-level deadlines to preserve intended local wall-clock times without altering explicit overrides
Lock Finalized Event Payload and Downstream Reactions
Given an occurrence transitions to Locked due to quorum or fallback When the Lock Finalized event is published to the event bus Then the event payload includes: meeting_id, occurrence_id, selected_slot_id, previous_state, new_state=Locked, lock_reason, deadline_ts, quorum_summary, idempotency_key, correlation_id, emitted_at And the Notifications consumer sends updated invites/notices only for the selected slot and does not send duplicates And the CalendarSync consumer cancels holds for all non-selected candidate slots and updates the locked slot once And both consumers process the event exactly once using the idempotency_key to deduplicate
Quorum Rule Recognition (Required Attendees and Percentage Threshold)
Given a quorum rule of type required_attendees with a specified list of attendees When all required attendees mark a slot as available before or at the deadline Then quorum is considered met for that slot Given a quorum rule of type percentage with threshold 70% and N invitees When ceil(0.7*N) or more invitees mark a slot as available before or at the deadline Then quorum is considered met for that slot Given mixed rules where both required_attendees and percentage must be satisfied When both conditions are true for a slot before or at the deadline Then quorum is considered met; otherwise quorum is not met
Fairness-Based Tie-Breaking
"As a distributed team lead, I want ties resolved using fairness and attendance impact so that after-hours burden is shared equitably and attendance stays high."
Description

When multiple slots meet quorum with equivalent attendance, resolve ties using TimeTether’s fairness rotation and attendance impact model. Combine factors such as historical after-hours burden per attendee, presence of required attendees, and overall attendance score into a deterministic tie-break formula with configurable weights. Guarantee deterministic outcomes by establishing a precedence order and seeding with meeting series ID. Log the rationale (scores, weights, chosen factors) with each decision for transparency. This ensures equitable distribution of meeting burden across time zones while maintaining high attendance likelihood.

Acceptance Criteria
Deterministic Outcome via Series Seed
Given a meeting series with ID "S" and exactly two or more candidate slots that meet quorum with identical attendance metrics And all factor inputs (attendance scores, required attendee presence, after-hours burden) are equal between the tied slots When the tie-break algorithm is executed multiple times across different nodes/environments for the same series ID "S" Then the same slot is selected every time And when the same evaluation is executed with a different series ID "S2" Then the selected slot is consistent across runs for "S2" and is allowed to differ from the selection for "S"
Required Attendee Weighting Priority
Given a configured weight for "required attendee presence" greater than zero And two candidate slots A and B meet quorum with equal overall attendance counts And slot A includes all required attendees while slot B is missing at least one required attendee When the tie-break algorithm is executed Then slot A is selected And the decision rationale lists the required-attendee factor, its weight, and the computed factor scores for slots A and B
Fairness Burden Minimization
Given attendee historical after-hours burden data is available for all invitees And a configured positive weight for "after-hours burden minimization" is set And two or more candidate slots meet quorum with equivalent attendance counts When the tie-break algorithm is executed Then the algorithm calculates the weighted incremental after-hours burden per attendee for each slot and totals it into a fairness score And the slot with the lowest fairness score is selected And the rationale includes per-attendee incremental after-hours minutes and the aggregated fairness score for each candidate slot
Configurable Weights Affect Outcome
Given a weight set W1 is active with weights for factors [required presence, attendance score, after-hours burden] And with W1 the algorithm selects slot X among tied candidates When the active weight set is changed to W2 and the tie is re-evaluated with identical inputs Then the algorithm recomputes scores using W2 And if the total scores change order under W2, the selected slot updates accordingly And the decision rationale records the active weight set identifier and the weights used
Deadline Tie-Break Execution and Actions
Given a decision deadline D has been configured for the occurrence And at time D multiple candidate slots still meet quorum with equivalent attendance When the deadline is reached Then the tie-break algorithm executes within 2 seconds And the winning slot is locked And all conflicting slots for that occurrence are canceled And updated invites are sent to attendees And the decision rationale is logged and linked to the occurrence
Transparent Rationale Logging
Given a tie-break decision has been made When the coordinator views the decision details in the audit log or via API Then the record includes: meetingSeriesId, occurrenceId, decisionId, timestamp, candidate slot IDs, per-factor scores per slot, factor weights, total score per slot, selectedSlotId, precedence order applied, seed details, and algorithm version And the record is immutable (subsequent write attempts are rejected) And the record is retrievable via API within 1 second using decisionId or occurrenceId
Auto-Cancel & Invite Update
"As an attendee, I want updated invites to be sent and conflicts canceled automatically so that my calendar stays accurate without extra steps."
Description

Upon lock finalization, automatically cancel any now-conflicted occurrence and issue updated invites for the winning slot. Sync changes bi-directionally with Google and Microsoft calendars (Graph/CalDAV/ICS), preserving meeting IDs where possible to avoid duplicate entries and maintain history. Include a clear summary of the selected slot, rationale (e.g., quorum reached, tie-breaker applied), and one-click add/accept actions in email and Slack notifications. Respect existing RSVP states, de-duplicate notifications across channels, and provide idempotent operations with retries for partial failures. This keeps attendee calendars accurate and reduces coordination overhead.

Acceptance Criteria
Auto-cancel losing occurrence and send updated invites on lock
Given a recurring meeting with multiple candidate slots and a quorum threshold And quorum is reached for one slot before the decision deadline When TimeTether finalizes the lock for the winning slot Then the losing/conflicting occurrence in the same series is cancelled within 60 seconds And the cancellation reason references the winning slot and lock time And an updated invite for the winning slot is issued to all required and optional attendees within 60 seconds And attendee calendars show only the winning slot in the series after sync completes
Preserve event identity and avoid duplicates across providers
Given an existing calendar event/series in Google or Microsoft When TimeTether updates the winning slot and cancels the losing occurrence Then the original event UID/ID (or series master ID) is preserved where the provider allows And updates occur in-place without creating duplicate events for any attendee And ICS/CalDAV feeds use a stable UID across updates And the event description/history contains an audit note summarizing the change without resetting the event thread
Bi-directional sync reliability, idempotency, and consistency SLO
Given connected Google and/or Microsoft accounts for attendees When updates and cancellations are pushed Then provider APIs are called with exponential backoff and up to 3 retries on transient errors (HTTP 429/5xx) And operations use idempotency tokens so replays do not create extra events or notifications And inbound RSVP changes made in provider clients are reflected in TimeTether within 2 minutes And calendars for 95% of attendees reflect the correct state within 2 minutes and 99% within 5 minutes And failures beyond retry are logged with correlation IDs and surfaced to the coordinator without blocking other attendees
Notification content and one-click action completeness
Given notifications are enabled for email and Slack When the winning slot is locked Then each user receives a notification that includes the selected slot (localized to the user's timezone), the rationale (e.g., quorum reached, tie-breaker applied), and a one-click add/accept action And Slack interactive buttons and email deep links execute the accept/add action successfully without extra confirmation steps And links deep-link to the correct meeting instance and preserve the event ID And notifications render the summary and CTA correctly on mobile and desktop clients
Respect and map existing RSVP states correctly
Given attendees have prior RSVP states on the series When the losing occurrence is cancelled and the winning slot invite is issued Then Declined remains Declined and is not auto-changed And Accepted or Tentative are not auto-flipped to Accepted for the new time; they are set to Needs Action or Tentative based on provider capabilities And RSVP state mappings are correct for Google (accepted, tentative, needsAction, declined) and Microsoft (accepted, tentative, none, declined) And no attendee is forced to re-accept without an explicit action
Cross-channel notification de-duplication
Given a user can be notified via both email and Slack When lock finalization occurs Then the user receives no more than one notification per meeting across channels within a 10-minute window And user/org channel preferences are honored when selecting the channel; if no preference exists, default to email And repeated updates within the 10-minute window are coalesced into a single updated message And no duplicate emails or duplicate Slack messages are sent for the same lock event
Quorum Progress Tracking & Nudges
"As a coordinator, I want to see quorum progress and send smart nudges so that we reach a decision faster without spamming the team."
Description

Display real-time quorum progress in the scheduling UI, showing percentage reached, required attendee responses, and remaining invitees. Provide smart, rate-limited nudges via email and Slack/Teams to non-responders, honoring quiet hours and user preferences. Embed one-click slot selection in messages with secure, expiring deep links. Allow coordinators to adjust nudge cadence and deadline reminders. Track delivery, opens, and conversions to optimize messaging. This accelerates decision-making without spamming users and integrates with TimeTether’s existing notification framework.

Acceptance Criteria
Real-Time Quorum Progress Display
Given an active meeting occurrence with a defined quorum rule (e.g., 70% of invitees or all required attendees) When any invitee submits or changes a slot selection Then the Quorum Progress module updates within 5 seconds to show: - Leading slot quorum percent = floor((count of unique invitees who selected the leading slot ÷ quorum target) × 100) - Required attendees: responded/total and names of required attendees still pending - Remaining invitees count (total invitees − respondents) And the timestamp of last update is displayed And values match the server state within one polling/streaming interval And on network loss, a stale indicator appears and auto-retries until reconnected
Rate-Limited Nudges Respecting Quiet Hours & Preferences
Given there are invitees who have not responded to the occurrence And channel permissions and user preferences are available When the nudge scheduler runs Then no user receives more than 1 nudge per 24 hours per channel for this occurrence And no user receives more than 3 total nudges before the decision deadline And no nudges are delivered during the user's configured quiet hours (default 20:00–08:00 local) unless the user has opted to allow after-hours And channels opted out by the user are not used And Slack/Teams nudges are sent only to connected accounts; otherwise they are skipped (or fall back per policy) And each scheduled nudge includes the next eligible send window per user timezone
Secure One-Click Slot Selection via Expiring Deep Links
Given a non-responder receives a nudge via email or Slack/Teams with embedded one-click slot selection When the invitee clicks a slot action Then their selection is recorded without additional login using a single-use token bound to invitee+occurrence+slot And the token is at least 128-bit entropy, transmitted over HTTPS, and expires at the earlier of the decision deadline or 7 days after issuance And replay or tampered tokens are rejected with HTTP 403 and a safe error page offering a fresh authenticated path And a confirmation screen shows the recorded selection and current quorum progress And a conversion event is logged with message_id, channel, user_id, occurrence_id, slot_id, and timestamp
Coordinator-Configurable Nudge Cadence and Deadline Reminders
Given a coordinator opens Nudge Settings for an occurrence When they set cadence (every 4–72 hours), max nudges (1–5), and enable a final reminder (e.g., 2–24 hours before deadline) Then validation enforces allowed ranges and conflicting settings are prevented And saving updates the schedule immediately for future sends and displays the next send time in the coordinator's timezone and each recipient's local timezone window And disabling nudges cancels all pending jobs within 1 minute And changes are audit-logged with before/after values and actor
Delivery, Open, and Conversion Analytics
Given nudges are sent across channels When delivery outcomes are returned by providers or the app Then delivery status (delivered, bounced, failed, throttled) is stored per message with timestamp and provider metadata And opens are captured for email via tracking pixel and for Slack/Teams via supported read events; unsupported channels show N/A And conversions are recorded on valid one-click actions and mapped to the originating message_id And coordinators can view per-occurrence metrics: send count, delivery rate, open rate, conversion rate by channel And raw events are retained for at least 90 days for analysis
Auto-Cancel Nudges After Response and Race-Condition Handling
Given a non-responder submits a slot selection via any channel When the system receives the response Then all scheduled nudges for that user and occurrence are cancelled within 1 minute And any in-flight send window is de-duplicated to prevent multiple deliveries within a 10-minute grace period And subsequent nudges target only remaining non-responders and adjust counts in the UI accordingly And cancellations and deduplications are logged
Notification Framework Integration and Channel Fallbacks
Given the existing notification framework for queuing, retries, and routing When nudges are dispatched Then the framework's retry policy (exponential backoff up to 3 attempts, max 24 hours) is applied per message And if Slack/Teams delivery fails due to disconnected account or permission, the system falls back to email once and records the fallback And if email hard-bounces, no further attempts are made via that channel for the occurrence And all outcomes are surfaced to the analytics layer and coordinator UI
Audit Trail & Admin Override
"As a coordinator, I want an audit trail and the ability to override within a grace period so that I can correct mistakes and explain decisions."
Description

Record a tamper-evident timeline of quorum evaluations, responses, tie-break calculations, lock events, and notification actions. Expose an audit view that explains why a slot won, including scores and required attendee status. Allow authorized coordinators to override a locked decision within a configurable grace period, triggering automatic rollback/redo of cancellations and invites with updated rationale messaging. Enforce role-based permissions, log overrides, and support export of the decision log for compliance. This adds accountability and a safe recovery path for mistakes or late-breaking constraints.

Acceptance Criteria
Tamper-Evident Decision Timeline Logging
Given a meeting occurrence using Quorum Pick When any of the following events occur: quorum evaluation start/end, attendee response recorded/updated, tie-break calculation, slot lock, cancellation, invite send/update, notification dispatch Then the system appends a log entry with event_type, timestamp (ISO 8601 UTC), actor (user ID or system), meeting_id, occurrence_id, slot_id (if applicable), input parameters snapshot, computed scores (if applicable), and resulting action And then each entry includes a SHA-256 hash of its contents and the previous entry’s hash for that occurrence (hash chain) And then any modification or deletion of a prior entry causes chain verification to fail for that occurrence And then log entries are queryable via the audit API within 5 seconds of the event
Audit View Justifies Winning Slot
Given a locked decision exists for an occurrence When an authorized user opens the Audit View Then the view displays the winning slot, the quorum threshold, and the timestamp it was achieved And then the view lists required attendees with their response statuses and whether quorum criteria were satisfied And then each candidate slot shows attendance score, fairness score, and tie-break rationale text with component weights and totals And then the ordered event timeline mirrors the last 50 relevant log entries for that occurrence And then the page loads within 2 seconds for logs up to 500 events
Admin Override Rollback/Redo within Grace Period
Given a configurable grace period (0–72 hours) is set and a slot has been locked And given the current time is within the grace period When an authorized coordinator submits an override to a different slot and provides a rationale (minimum 10 characters) Then the system records an override log entry linking to the original decision and the rationale text And then the system cancels the previously locked slot’s invites, reinstates any prior cancellations as needed, and sends updated invites for the new slot And then the new slot is marked locked, notifications are dispatched, and the decision timeline reflects rollback/redo steps And then affected calendars reflect the change within 2 minutes And then if the request is outside the grace period, the override is blocked with an error indicating grace period expiration
Role-Based Permissions for Audit and Override
Given role definitions of Viewer, Coordinator, and Org Admin When a user requests access to the Audit View Then Viewer and above can view the audit timeline and export previews And then only Coordinator and Org Admin can initiate overrides And then only Org Admin can modify grace period settings And then all access attempts and actions (allow/deny) are logged with user ID, role, timestamp, and resource And then unauthorized actions return HTTP 403 with no sensitive data in the response body
Exportable Decision Log for Compliance
Given an authorized user is viewing an occurrence’s audit log When the user requests an export Then the system generates CSV and JSON files containing the ordered log entries, all logged fields, and the per-entry and chain hashes And then the export respects applied filters (date range, event types) And then a file-level SHA-256 checksum is provided for each export And then the export is available for download within 60 seconds for logs up to 10,000 entries
Override Rationale Included in Participant Notifications
Given an override succeeds for an occurrence When invites and notifications are sent Then each recipient receives a single consolidated message per channel that states the new slot, cancellation of the prior slot, and the override rationale And then the message includes an impact summary showing change in expected attendance and required attendee satisfaction status And then messages are localized to the event’s language setting with English as fallback And then no recipient receives duplicate notifications on the same channel
Hash Chain Verification Endpoint
Given an occurrence ID with existing decision log entries When an authorized caller invokes the verification endpoint Then the endpoint recomputes the hash chain and returns a result of Pass or Fail And then on failure the response includes the index/ID of the first invalid entry and the expected vs. actual previous-hash values And then the response time is under 2 seconds for logs up to 5,000 entries

Nudge Guardrails

Every nudge is policy‑smart. Proposed alternatives are pre‑checked against Burn Caps, Series Caps, Spend Guardrails, and DST risks to avoid after‑hours creep. If no fully compliant option exists, the message includes a one‑tap, pre‑filled exception request for approvers. Results: humane choices by default, fewer policy detours, and clean audit trails.

Requirements

Policy Rule Engine for Nudge Validation
"As a remote team lead, I want nudge suggestions to be pre-checked against all scheduling policies so that the options I see are compliant without manual review."
Description

Provide a low-latency rules engine that evaluates all candidate meeting slots against organizational policies (Burn Caps, Series Caps, Spend Guardrails), participant work windows, and fairness constraints before a nudge is sent. The engine accepts participants, time zones, declared work hours, recurrence pattern, and policy context, and returns a ranked list of compliant alternatives with explicit exclusion reasons for rejected slots. Integrates with TimeTether’s scheduling core synchronously (<200ms per evaluation), supports holiday/PTO calendars, and exposes a deterministic API for consistent evaluations across web, Slack, and email entry points. Ensures only policy-compliant options are proposed by default.

Acceptance Criteria
Low-Latency Ranked Compliance Evaluation
Given a request including participants with time zones, declared work windows, recurrence pattern, holiday/PTO calendars, and policy context When the engine evaluates up to 100 candidate slots Then the evaluation completes within 200ms p95 and 400ms p99 measured over 1,000 requests in a production-like environment And the response contains a ranked list of compliant alternatives ordered by compliance_score descending with stable tie-breakers (slot_start UTC, participant_id) And non-compliant slots are excluded from the list and appear in exclusions[] with explicit reasons codes and data (policy_code, observed_value, threshold) And the response includes request_id and evaluation_timestamp in ISO-8601 UTC
Deterministic Outputs Across Entry Points
Given identical inputs and rule-set version across Web, Slack, and Email entry points When the engine evaluates the same set of candidate slots Then the compliant list, ordering, scores, and exclusions (including reason codes and payloads) are byte-for-byte identical across channels And repeated calls with the same inputs and version produce identical outputs And the response includes ruleset_version and result_hash to assert determinism
Policy Enforcement: Burn Caps, Series Caps, Spend Guardrails
Given policy context specifies burn caps, series caps, and spend guardrails When a candidate slot causes any cap to be exceeded over the specified recurrence window Then that slot is rejected with reasons including policy_code, window, threshold, and projected_value And a slot exactly at the cap (projected_value == threshold) is accepted; values above are rejected And all returned compliant options have zero policy violations across all enforced caps
Work Windows, Holidays, PTO, DST Safety
Given each participant has IANA time zone, declared work windows, and is associated with organization holiday calendar and individual PTO events When evaluating candidate slots Then any slot outside any participant’s declared work window is rejected with reason AFTER_HOURS and includes participant_id and offending_local_time And any slot on an org holiday or participant PTO is rejected with reason NON_WORKING_DAY and includes calendar_source And slots crossing DST transitions that would shift any participant outside their declared window are rejected with reason DST_RISK; otherwise they are marked DST_SAFE=true
Fairness Rotation Compliance
Given fairness constraints include rotation order, max_after_hours_share, and weighting rules When proposing alternatives for a recurring meeting of K occurrences Then the rotation assigns the first occurrence to the next participant in order and advances deterministically thereafter And for the next K occurrences each participant’s projected after-hours share is <= policy.max_after_hours_share And each returned alternative includes fairness_score >= policy.min_fairness_score and a rotation_preview[] showing the first 3 occurrences mapping to participant_ids
No-Compliant-Options Exception Path
Given all candidate slots are non-compliant When the engine completes evaluation Then the compliant alternatives list is empty and exclusions[] contains entries for 100% of candidates with explicit reasons And the response includes exception_request with approver_group, policy_violations[], suggested_slot (lowest violation_score), and deeplink suitable for one-tap approval And exception_request is only present when no compliant alternatives exist
Deterministic API Contract and Validation
Given the API is invoked Then requests must validate against schema: participants[], time_zones (IANA), work_windows, recurrence_pattern, policy_context required; invalid inputs return 400 with error_code And responses include api_version and ruleset_version; mismatched or deprecated versions return 409 with upgrade hint And idempotency_key returns the same request_id and response on retry within 5 minutes And the API supports up to 25 participants and 500 candidate slots per request; requests exceeding limits return 413 with error_code LIMIT_EXCEEDED
Fairness-Preserving Alternative Generation
"As a product manager scheduling a recurring meeting, I want a small set of fair, compliant alternatives so that the burden doesn’t fall on the same people each cycle."
Description

Generate a compact set of alternative meeting times that maintain TimeTether’s fairness rotation while satisfying guardrails. The generator must balance time-zone equity, rotate burdens week-over-week, and cap the number of options to reduce decision fatigue. It should degrade gracefully when constraints are tight by expanding search windows within policy limits and by clearly labeling the fairness impact. Integrates with the rule engine to filter and rank results and with the invite system for one-tap confirmation.

Acceptance Criteria
Compact, Fair, Guardrail-Compliant Alternatives
Given a meeting request with participants across at least two time zones, active Burn Caps, Series Caps, Spend Guardrails, and declared work windows When the generator produces alternatives Then it returns between 2 and 5 options inclusive And each option keeps all participants within their declared work windows And no option violates Burn Caps, Series Caps, Spend Guardrails, or DST risk rules And options are ordered by rule engine score descending And the top option has the lowest fairness-impact score among the set
Rotation Preservation Across Weeks
Given an existing fairness rotation plan and current week index When alternatives are generated for this week Then all options align with the same participant rotation order as the baseline schedule And selecting any option will not assign early/late burden to the same participant in two consecutive weeks And each option displays a fairness impact label including net shift in minutes per participant or region And the fairness score delta for each option is within the configured tolerance
Graceful Degradation Under Tight Constraints
Given the default search windows yield zero compliant alternatives When the generator expands windows within policy limits Then expansion proceeds in 15-minute increments up to the configured maximum per participant And any option included post-expansion is policy-compliant And each returned option is labeled "Expanded Window" with per-participant expansion amounts in minutes And if no compliant options exist after maximum expansion, zero options are returned and the exception path is prepared
DST Risk Avoidance
Given any participant has a DST transition within +/- 14 days of the candidate date When evaluating candidate alternatives Then exclude any option that would place any participant outside their work window due to the DST shift And exclude any option where the local wall-clock time would change by 30 minutes or more relative to baseline due to DST And if all otherwise compliant candidates are excluded due to DST risk, trigger the exception path
Rule Engine Filtering and Ranking Integration
Given the rule engine is reachable When generating and evaluating candidate alternatives Then 100% of candidates are submitted to the rule engine for decisioning and scoring And only candidates with decision "allow" are included in the final set And final ordering equals the rule engine's score order (highest first) And for each of the top three options, include machine-readable reasons from the rule engine
One-Tap Invite Confirmation
Given a user taps confirm on a generated alternative When creating the calendar event Then the event reflects the selected start and end times with correct time zones for all participants And meeting title, description, attendees, and conferencing link are preserved from the original request And invitations are dispatched within 2 seconds of tap with a success confirmation to the user And on success, update the fairness rotation state; on failure, do not mutate fairness state
Pre-Filled Exception Request When No Compliant Option Exists
Given no compliant alternatives exist after maximum search window expansion When presenting the nudge Then include a one-tap exception request action And pre-fill the request with meeting metadata, the specific violated policies, and the least-risk proposed time And upon submission, log an audit record with requester, timestamp, violated policies, proposed time, and context And deliver the exception request to approvers within 10 seconds with approve and deny actions
One-Tap Exception Request Flow
"As a team coordinator, I want a pre-filled exception request when compliance blocks all options so that I can quickly obtain approval without re-entering details."
Description

When no fully compliant option exists, embed a one-tap exception request in the nudge across channels (web, Slack, email). Auto-populate the request with the attempted slots, specific policy violations (e.g., exceeds Burn Cap by 30 minutes), impacted participants, and proposed mitigations. Route requests to the correct approvers per org policy with SLAs, reminders, and fallback escalation. Reflect approval/denial in the scheduling thread in real time and update the audit log automatically.

Acceptance Criteria
Cross-Channel One-Tap Exception Control
Given a nudge is generated and no fully compliant scheduling option exists per Nudge Guardrails When the nudge is rendered on web, Slack, or email Then a "Request Exception" one-tap control is visible on all channels and enabled Given the user activates the "Request Exception" control on any channel When the system submits the request Then the exception request is created without requiring additional fields And the user receives in-channel acknowledgement within 2 seconds Given at least one fully compliant option exists When the nudge is rendered Then the "Request Exception" control is not shown
Auto-Populated Exception Payload Accuracy
Given an exception request is created Then the payload includes: attemptedSlots (array of ISO 8601 start/end with participant local offsets), violations (each with policyType ∈ {Burn Cap, Series Cap, Spend Guardrail, DST Risk}, quantified delta, calculation basis), impactedParticipants (userId, name, timezone), proposedMitigations (e.g., rotate next week, shorten duration by 15m), originatingChannels {web|slack|email}, requesterId, and requestContextId Given the payload is built Then all required fields are non-null and pass schema validation And the quantified deltas match recomputed policy checks for each violation within ±1 minute tolerance
Approver Routing and SLA Assignment
Given org policy defines approver rules by team, cost center, and meeting type When an exception request is created Then the system resolves the approver chain matching the requester’s attributes and meeting metadata And assigns the policy-defined SLA duration and reminder schedule to the request Given the approver cannot be resolved When routing is attempted Then the request is assigned to the policy-defined fallback approver group And the event is logged as "fallbackRouting" with reason
Reminders and Fallback Escalation
Given an exception request with SLA = 8h and reminders at 4h and 30m before breach per policy When time elapses Then approvers receive reminder notifications at T-4h and T-30m via their preferred channels Given the SLA elapses without decision When T reaches SLA Then the request escalates to the fallback approver within 5 minutes And the original approver is notified of escalation Given an approver declines Then no escalation occurs and the request is closed with status = Denied
Real-Time Decision Reflection in Thread
Given an approver approves the request When approval is recorded Then the scheduling thread on web, Slack, and email updates within 5 seconds to show Approved with approver name, timestamp, and the approved slot And the meeting is scheduled for the approved slot with invites sent Given an approver denies the request When denial is recorded Then the scheduling thread updates within 5 seconds to show Denied with reason And compliant alternatives (if any are available) are re-suggested Given the request is approved or denied Then the "Request Exception" control in the thread is removed or disabled
Audit Log Completeness and Integrity
Given any exception request lifecycle event (created, routed, reminded, escalated, approved, denied) When the event occurs Then an immutable audit log entry is written with requestId, eventType, actorId, timestamp (UTC), channel, payload snapshot hash, and policy version Given the audit log is queried by requestId Then the full event timeline is returned in chronological order with no missing steps Given an unauthorized user attempts to modify an audit record Then the operation is rejected and logged as a security event
DST Risk Detection and Messaging
Given any attempted slot crosses a DST transition for any participant When the exception request is prepared Then the violations list includes a DST Risk entry naming the participant(s), region(s), and the shift magnitude And the nudge message includes a DST caution label and a proposed mitigation (e.g., avoid transition week)
DST Risk Detection and Safeguards
"As an engineering lead, I want nudges to account for DST shifts so that a time that works today doesn’t accidentally become after-hours next month."
Description

Continuously detect upcoming daylight saving time changes for all participants across the recurrence horizon and simulate post-shift local times. Automatically exclude or flag slots that would become after-hours post-DST, and prefer resilient windows that remain within work hours before and after the shift. Surface clear DST warnings in the nudge and suggest auto-adjust rules where allowed by policy. Integrates with the rule engine and fairness generator.

Acceptance Criteria
Horizon-wide DST Change Detection
Given a recurring series with participants across time zones that have DST transitions within the next 26 weeks When the scheduler evaluates the recurrence horizon (on create/edit and at least every 24 hours) Then the system retrieves authoritative DST transition data for each participant’s time zone And simulates local times for every future occurrence both pre- and post-transition And stores a per-occurrence DST risk assessment (None | Warning | After-Hours) in an auditable log
Exclude and Flag After-Hours Post-DST Slots
Given a candidate slot is within work hours pre-shift but becomes after-hours for any participant post-DST according to their policy-defined work window When generating proposed alternatives for a nudge Then the slot is excluded from auto-selectable options and labeled with tag "DST-AfterHours" And the UI surfaces the impacted participant(s) and the specific occurrence dates And if all candidate slots are impacted, then include a one-tap pre-filled exception request referencing the DST constraint
Prefer Resilient Windows Across DST
Given multiple compliant windows exist for a recurring meeting When ranking and selecting options Then any window that remains within all participants’ work hours both before and after DST transitions is ranked above any window that violates at least one occurrence And the top 3 suggestions must be fully resilient if at least 3 resilient options exist And partially resilient options are only shown after fully resilient options
Surface Clear DST Warnings in Nudge
Given the suggested options are within 8 weeks of a DST change or include simulated time shifts for any participant When rendering the nudge message Then include a DST warning section listing affected regions, effective dates, and before→after local times for impacted participants And include a one-tap "Auto-adjust to keep within work hours" control where policy permits And if policy does not permit, include a one-tap pre-filled exception request
Auto-Adjust Rule Suggestion and Application
Given policy allows auto-adjust rules for recurring meetings When the organizer taps "Auto-adjust to keep within work hours" Then the system creates a series-level rule that shifts post-DST occurrences to maintain each participant within their defined work window And validates the change against Series Caps, Burn Caps, and Spend Guardrails before applying And updates all future calendar invites within 60 seconds And records a tamper-evident audit entry including prior and new local times And if any guardrail would be exceeded, then block the change and surface a pre-filled exception request
Fairness Integration Under DST Constraints
Given the fairness rotation is enabled for the team When DST-driven exclusions reduce feasible slots Then the fairness generator recalculates rotation to remain within ±1 occurrence variance per participant over the recurrence horizon And does not increase any participant’s after-hours burden beyond policy-defined limits And if the constraints are unsatisfiable, then present "No compliant option" and attach a pre-filled exception request citing the DST reason
Compliance Audit Logging and Reporting
"As a compliance officer, I want complete, searchable logs of nudge decisions so that I can verify policy adherence and resolve disputes quickly."
Description

Record an immutable audit trail for each nudge: inputs (participants, time windows, policy version), evaluation outcomes, reasons for exclusion, options shown, user selections, exception requests, approver actions, and timestamps. Support retention policies, PII minimization/redaction, and RBAC-based access. Provide export endpoints (CSV/JSON) and a dashboard for compliance reviews and incident investigations. Ensure logs are tamper-evident and correlate with invite IDs.

Acceptance Criteria
Immutable Audit Trail Per Nudge
Given a nudge is evaluated by Nudge Guardrails When options are generated, filtered, selected, or an exception is requested/acted upon Then an append-only audit event is written containing: nudge_id, correlation_id, participants (hashed/pseudonymized), participant_timezones, meeting_time_windows, policy_version_ids, evaluation outcomes per policy, reasons_for_exclusion per candidate, options_shown with ranks/scores, user_selection or dismissal, exception_request payload (if any), approver_action(s) (if any), invite_id(s) (if created), and RFC3339 timestamps for each lifecycle step And the event is assigned an immutable event_id and included in a tamper-evident chain And the write completes within 200ms p95; on failure, it is retried up to 3 times with exponential backoff and emits an alert and metric
Tamper-Evidence and Integrity Verification
Given an existing audit log range When the verify_integrity API is called for a time window or event range Then the API returns a proof (chain root, segment hashes, signature) and status "verified" if no tampering is detected And any attempt to update or delete an existing audit event is rejected with HTTP 409 and recorded as a security event And if any event has been altered, verify_integrity returns status "failed" with the first offending event_id and proof of mismatch
RBAC Access Controls with PII Minimization
Given a user requests audit data When the user has role Auditor or Compliance_Analyst within tenant scope Then they may read audit fields with PII (emails, calendar_ids) redacted/masked by default and participant identifiers shown as hashes And elevation to view unredacted PII requires Admin or Approved_Override role, active MFA, and a justification; unmasked responses are watermarked and the access is itself logged And users without sufficient role receive HTTP 403 or are restricted to their own nudges with redacted PII And every access is logged with user_id, role, fields accessed, purpose, and timestamp
Retention and Redaction Enforcement
Given tenant-specific retention policies for audit_events and PII_overlays are configured When the nightly retention job runs Then events past retention have PII fields irreversibly redacted while non-PII metadata is preserved for correlation And items under legal_hold are excluded until the hold is cleared And a retention report is produced with counts redacted/purged by category, and 100% of expired items are processed within 24 hours
Audit Log Export APIs (CSV/JSON)
Given an authorized request to /exports/audit with filters (date_range, nudge_id, invite_id, participant_hash, policy_version, outcome, approver_id) When format is JSON or CSV Then the service returns a paginated or streaming export with schema_version header, column dictionary, total_count, and checksum (SHA-256) And performance: up to 100k rows delivered within 60s p95 via streaming And security: export requires scoped token, presigned URLs expire in <=15 minutes, and CSV cells are sanitized to prevent formula injection And exported rows match query filters and row_count equals total_count
Compliance Review Dashboard for Investigations
Given a compliance analyst opens the audits dashboard When searching by invite_id, nudge_id, participant_hash, or date range with filters (policy outcome, exception status, approver) Then results load within 3s p95 for a 30-day window and support sort, filter, and drill-down to a lifecycle timeline And each record displays evaluation outcomes, reasons for exclusion, options shown, selected option, exception request details, approver actions, timestamps, DST risk flags, and links to invite/calendar event And the analyst can export the current result set to CSV/JSON with identical filters applied
End-to-End Correlation by Invite ID and Series
Given an invite_id from a recurring series When the correlation API is queried with invite_id or series_id Then all related nudges across the series are returned with a shared correlation_id and fairness rotation indicators And the timeline shows proposal, selection, exception, approval/denial, and final invite dispatch with timestamps in both UTC and local participant timezones And zero orphaned invites appear; every invite_id in the series traces to a nudge audit chain
Policy Configuration and Versioning UI
"As a workspace admin, I want to configure and version scheduling policies so that guardrails reflect our current rules without breaking existing series."
Description

Deliver an admin interface to define and manage Burn Caps, Series Caps, Spend Guardrails, and work windows by org, team, role, and location. Support effective-dated versions, change history, diff views, draft/publish workflow, and a sandbox to test policies against sample schedules. Provide bulk import/export and an API for automated policy updates. All changes propagate in real time to the rule engine with version pinning for reproducibility.

Acceptance Criteria
Scoped Policy Definition (Burn Caps, Series Caps, Spend Guardrails, Work Windows)
Given I am an Org Admin with Policy:Manage permission When I create or edit a rule of type Burn Cap, Series Cap, Spend Guardrail, or Work Window Then I can assign scope by org, team, role, and location with include/exclude selectors And the UI validates required fields and value ranges per rule type (e.g., non-negative caps, valid currency, valid time ranges) And conflicting rules within the same effective scope are blocked with a descriptive error before save And work windows capture timezone using an IANA timezone and display local conversions without ambiguity And on save a Draft policy version is created with a unique Version ID and timestamps
Effective‑Dated Versioning, History, and Diff View
Given an existing Draft policy version When I set an Effective From date-time and Publish Then the system creates an immutable Published version with Version ID and Effective From And change history records author, timestamp, change reason, and affected scopes And a Diff view shows field-level changes and a JSON Patch between the prior and new versions And previous versions remain read-only and discoverable by Version ID, date range, and author And restoring a prior version creates a new Draft derived from that version (no in-place edits) And Effective From is stored in org default timezone and displayed with explicit timezone labels in the UI
Draft/Publish Workflow and Permissions
Given role-based permissions are configured When a user without Publish permission attempts to publish a Draft Then the action is blocked with a permission error and the Draft remains unchanged And when an approver with Publish permission approves a fully validated Draft Then the status transitions to Published immediately or Scheduled if Effective From is in the future And publishing is blocked if required metadata (e.g., change reason) is missing or scope conflicts exist, with precise validation messages And Drafts with an open approval request cannot be edited except by withdrawal or rejection, and all actions are recorded in history
Real‑Time Propagation and Version Pinning
Given a policy version transitions to Published When the publish event occurs Then the rule engine receives and acknowledges the update within 3 seconds p95 and 10 seconds p99, with up to 3 retries on transient failures And all new evaluations (scheduling/nudges) use the active effective version at evaluation time unless a request pins a Version ID And evaluations tied to a pinned Version ID continue using that version regardless of later publishes And a propagation status indicator shows Success or Failed; on failure, admins are alerted and no partial application occurs
Sandbox Policy Test Against Sample Schedules
Given I select a policy Version ID (Draft or Published) and provide sample schedules across timezones When I run the sandbox test Then the system evaluates compliance against Burn Caps, Series Caps, Spend Guardrails, and Work Windows, accounting for DST transitions And it returns a deterministic report with overall pass/fail, per-entity results, violation details (rule type, scope, rule ID), and suggested compliant alternatives when available And sandbox execution produces no side effects (no policy changes, invites, or notifications) And results can be exported as JSON/CSV with a test hash and the Policy Version ID for reproducibility
Bulk Import/Export of Policies
Given I have policy templates for CSV and JSON When I upload an import file up to 10 MB or 5,000 rows Then a Dry Run validates all rows and returns row-level errors and warnings without creating any versions And when I Commit a validated file, the import is atomic per file: either all rows create a single Draft version or none are saved, with a summary report And idempotency is supported via a client-supplied key; re-submitting the same file with the same key does not create duplicates And Export produces a versioned bundle for a chosen Version ID or scope, including metadata and checksum for integrity verification
Policy Update API and Audit Trace
Given an authenticated client with the policy.update scope When the client calls POST/PUT/PATCH endpoints with valid payloads, an Idempotency-Key header, and optional dry_run=true Then the API validates and returns 200/201 with a Version ID (or 4xx with field-level errors), and no version is created in dry_run mode And all API-originated changes appear in UI history and diff views with actor set to the calling client, source=API, and request IDs And the API enforces configurable write rate limits and returns 429 when exceeded, and successful publishes propagate to the rule engine per real-time guarantees
Policy-Smart Nudge Messaging
"As a meeting organizer, I want clear explanations in the nudge about why times were suggested or rejected so that I can make a fast, informed choice."
Description

Craft human-centered nudge content that explains why certain options were filtered and why suggested times comply (e.g., within work windows, under Burn Cap). Include microcopy that highlights fairness rotation and DST safeguards, with accessible formatting and localization support. Present one-tap actions (accept, pick another, request exception) consistently across channels. Ensure message payloads include machine-readable metadata for clients to render badges and tooltips.

Acceptance Criteria
Filtered Options Rationale and Compliance Badges
Given a nudge with both filtered-out and suggested time options When the message is rendered Then each filtered-out option includes at least one human-readable reason and a corresponding reason_code in the payload from {OUTSIDE_WORK_WINDOW, EXCEEDS_BURN_CAP, EXCEEDS_SERIES_CAP, VIOLATES_SPEND_GUARDRAIL, DST_RISK} And each suggested option includes compliance microcopy and a compliance_codes array covering all applicable guardrails from {WITHIN_WORK_WINDOW, UNDER_BURN_CAP, SERIES_CAP_OK, SPEND_OK, DST_SAFE} And non-applicable badges are omitted And each badge includes tooltip_key and tooltip_text in the payload And the human copy and the reason/complaince codes stay in sync for all options
One-Tap Actions Consistent Across Channels
Given the channels {email, in-app, Slack, mobile push} When a nudge is delivered Then actions {Accept, Pick another, Request exception} are present with identical labels and the order Accept | Pick another | Request exception across channels And each action is completed in one user gesture And action targets carry nudge_id, channel, action_type, and locale as parameters And non-interactive clients render unique links for each action with the same telemetry And the server records an action receipt (action_type, nudge_id, user_id, channel, timestamp) with HTTP 200 and persists within 1 second
Pre-Filled Exception Request in No-Compliant Case
Given a nudge with zero fully compliant options When the message is generated Then "Request exception" is primary and opens a pre-filled request containing failed_guardrails[], affected_participants[], proposed_times[], risk_summary, approver_route, and justification_template And the submission endpoint accepts the request with HTTP 200 and returns request_id And the nudge payload includes exception_request.pre_filled=true and request_schema_version And if at least one compliant option exists, "Request exception" remains available but is not primary
Accessible Formatting Across Surfaces
Given any supported client When the nudge content is rendered Then all interactive elements have accessible names and roles And badge and text contrast ratios meet WCAG 2.1 AA And information is not conveyed by color alone; text equivalents or tooltips are present And the content is fully operable by keyboard, with visible focus and logical order And screen readers announce badge meaning and DST/fairness notes via accessible names And default English copy scores <= grade 8 readability
Localization and Internationalization
Given recipient locale settings When a nudge is generated Then all user-facing strings are sourced from localized resources using ICU MessageFormat And dates, times, and time zones are formatted per locale (12/24-hour) and DST notes are localized And pluralization and grammar are correct for {en, es, fr, de}; missing translations fall back to English without placeholder keys And RTL locales render directionally correct without layout breakage And payload includes locale and timezone per participant so clients can render localized badges and tooltips
Machine-Readable Metadata for Badges and Tooltips
Given the nudge payload contract v1.x When a nudge is generated Then the payload includes schema_version, nudge_id, participants[], and options[] And for each option: start, end, timezone, compliance_codes[], violation_codes[], and badges[] with {key, tooltip_key, tooltip_text, icon_key} And a fairness_rotation object is present with {applied, cohort, position, next_rotation_at} And clients can render badges and tooltips using payload fields without parsing human-facing text And the payload validates against the JSON Schema for v1.x
Fairness Rotation and DST Safeguards Microcopy
Given fairness rotation influenced the suggestions When the message is rendered Then the microcopy states the active rotation cohort and current position And a "Fairness Applied" badge is present with a tooltip describing the rotation And payload includes fairness_rotation.applied=true Given any option is within 14 days of a DST transition for any participant When the message is rendered Then a "DST Safe" or "DST Risk" badge is shown per option with a localized tooltip explaining the impact and mitigation

Live Rerank

As votes arrive in Slack or Teams, options reprioritize in real time based on who answered (required vs optional), fairness score, and predicted attendance. If a top choice becomes infeasible, TimeTether quietly swaps in the next best compliant slot—no new thread needed. Participants see a simple progress bar and always act on the most viable options.

Requirements

Real-time Vote Ingestion (Slack/Teams)
"As a participant, I want my vote in Slack or Teams to be instantly registered so that the options reorder in real time without leaving chat."
Description

Implement webhook-based event ingestion for Slack and Microsoft Teams that captures button clicks, message actions, and emoji votes, maps them to the correct poll, and persists normalized vote events with user identity, role (required vs optional), and timezone. Provide OAuth scopes and bot permissions, tenant mapping, and RBAC checks so only eligible users can vote. Ensure idempotency keys, deduplication, and ordered processing where available, with retries and exponential backoff on transient failures. Acknowledge platform callbacks within SLA (<3s) and store events for downstream scoring. Integrates with TimeTether’s identity graph and meeting context service.

Acceptance Criteria
OAuth Scopes, Bot Permissions, and Tenant Mapping
Given a new Slack workspace or Microsoft Teams tenant installs TimeTether, When the OAuth flow completes, Then the system records tenant_id, installation_id/bot credentials, and granted scopes/permissions for that tenant. Given the OAuth callback contains missing required scopes/permissions, When installation completes, Then the installation is rejected and no webhooks are registered for that tenant. Given multiple tenants are installed, When events are received, Then each event is mapped to exactly one tenant and cannot be accessed by other tenants. Given an installation is revoked, When subsequent events are received, Then the system rejects the events and persists no votes, and the tenant is marked as revoked.
Webhook Verification and <3s Acknowledgment
Given a Slack or Teams webhook event with a valid platform signature/token, When the event is received, Then the platform callback is acknowledged with HTTP 200 within 3 seconds. Given a webhook event with an invalid signature/token, When the event is received, Then the request is rejected with HTTP 401/403 and the event is not queued for processing. Given a Slack URL verification challenge, When it is received, Then the challenge response is returned within 3 seconds. Given processing takes longer than the SLA, When the event is received, Then acknowledgment is still sent within 3 seconds and processing continues asynchronously.
Capture and Normalize Vote Events (Buttons, Actions, Emojis)
Given a Slack interactive button click, message action, or emoji reaction add/remove, or the Microsoft Teams equivalents, When the event is received, Then a normalized VoteEvent is created with fields: event_id, platform, poll_id, option_id, user_platform_id, action (vote|unvote), source_timestamp. Given an event missing required fields to identify poll or option, When processed, Then the event is rejected and logged without persisting a vote. Given an unsupported event type, When processed, Then it is ignored and metered without erroring the webhook.
Identity, Role, and Timezone Enrichment
Given a received event with a platform user identifier, When processing the event, Then the user is resolved via the identity graph to internal user_id and tenant_id. Given a resolved user and poll, When processing the event, Then the role (required|optional) is fetched from the meeting context service and attached to the VoteEvent. Given a resolved user, When processing the event, Then the timezone is attached from the identity graph; if unavailable, from the platform profile; if still unavailable, the VoteEvent persists timezone as null and is flagged for backfill.
RBAC Eligibility Enforcement
Given an event from a user not on the poll’s roster or outside the tenant, When processed, Then the vote is rejected, no VoteEvent is persisted, and an RBAC violation is metered. Given an event from a required or optional participant for an open poll, When processed, Then the VoteEvent is persisted. Given an event for a closed or archived poll, When processed, Then the vote is rejected and no VoteEvent is persisted.
Idempotency, Deduplication, and Ordering
Given duplicate deliveries of the same platform event (same event_id or idempotency key), When processed, Then exactly one VoteEvent is persisted. Given multiple votes from the same user on the same poll options that arrive out of order and the platform provides sequence identifiers, When processed, Then events are applied in sequence order. Given multiple votes from the same user on the same poll without sequence guarantees, When processed, Then last-write-wins based on platform timestamp is enforced.
Reliable Processing, Retries, and Persistent Storage
Given a transient processing failure (e.g., database timeout or network error), When persisting a VoteEvent, Then the system retries with exponential backoff up to the configured max attempts. Given max retry attempts are exceeded, When persisting a VoteEvent, Then the event is moved to a dead-letter queue and is not lost. Given a successfully accepted event, When processing completes, Then the normalized VoteEvent is durably stored and available to the scoring service within 2 seconds.
Dynamic Prioritization Engine
"As an organizer, I want options to reprioritize based on who responded and fairness so that we converge on the best slot with the highest likelihood of attendance."
Description

Build a scoring service that recalculates and reorders candidate time slots on every vote event using a composite score: (a) required-attendee coverage and constraints satisfaction, (b) predicted attendance for optional attendees, and (c) fairness rotation score. Support configurable weights and tie-breakers, deterministic results, and sub-200ms per-event recompute at p95. Expose a stateless API that consumes the current poll state and emits a top-N ranked list plus confidence. Integrate with fairness rotation service and attendance predictor, and publish updates to the chat rendering layer.

Acceptance Criteria
Deterministic Top-N Re-Rank on Vote Event
Given a stable poll state S with configured weights W and tie-breakers T And a vote event V from any participant When the engine recomputes scores Then it returns a ranked list of the top N slots with deterministic order for identical (S, W, T, V) And the order is identical across repeated identical requests within 5 minutes And the response includes for each slot: slot_id, composite_score, component_scores {a,b,c}, confidence [0..1], rank, and reason_codes
Required Attendee Coverage and Constraints Enforcement
Given required attendees R with working windows and hard constraints When scoring candidate slots Then any slot violating a hard constraint for any required attendee is assigned composite_score=0 and excluded from top N And among remaining slots, higher required-attendee coverage strictly increases component (a) And coverage honors time zone conversions to each attendee’s local working windows
Predicted Attendance Incorporation for Optional Attendees
Given optional attendees O and an available attendance predictor When scoring candidate slots Then the predictor is invoked in batch once per recompute for all candidates And component (b) equals the sum of predicted attendance probabilities across O, clamped to [0, |O|] And if the predictor is unavailable, use last cached predictions (<=24h old) or historical averages and emit a WARN log and metric
Fairness Rotation Integration and Tie-Breaking
Given fairness deltas from the fairness rotation service When two or more slots have equal composite scores within epsilon=1e-6 Then tie-breakers are applied in order: higher fairness score, earlier start time, lower slot_id And component (c) is normalized to [0,1] and returned in component_scores And identical inputs produce the same final ordering
Configurable Weights with Runtime Reload
Given weight config W={a,b,c} with a+b+c=1 and version k in the config store When W is updated to version k+1 Then the engine loads k+1 within 5 seconds without restart And all subsequent recomputes use k+1 and include config_version=k+1 in responses And invalid configs (missing weights or sum != 1±0.001) are rejected and k remains active
p95 Recompute Latency Under Load
Given 1,000 active polls with 10 candidate slots each and median 12 voters When vote events arrive at 50 events/sec sustained for 5 minutes Then per-event recompute latency p95 <= 200ms and p99 <= 350ms measured at the API boundary And HTTP 5xx error rate <= 0.1% and timeouts (>=1s) <= 0.05% And CPU utilization <= 70% with no GC pause > 50ms during the test window
Stateless API Contract and Chat Update Publication
Given POST /rerank with poll_state, weights, tie_breakers, top_n, and request_id When processed Then the response is idempotent for the same request_id and inputs for 5 minutes And the service publishes a ranking_update to the chat rendering topic within 100ms of compute completion containing top_n, delta_from_previous, and next_best_fallback And if the current top choice becomes infeasible, the event flags swap_applied=true and includes the new top choice without creating a new thread identifier
Feasibility Guardrails & Auto-Fallback
"As a participant, I want TimeTether to automatically switch to the next viable option when the current top becomes impossible so that the thread stays clean and I only act on valid choices."
Description

Continuously validate that the top-ranked slot remains compliant with required-attendee availability, work windows, buffer rules, and calendar holds. If the top slot becomes infeasible, atomically promote the next best compliant slot and update the chat message in place—no new thread. Add hysteresis and minimum dwell time to avoid thrashing, and ensure the swap updates provisional holds/invites consistently. Provide rollback if new information restores feasibility. All changes must be transactionally applied and auditable.

Acceptance Criteria
Continuous Feasibility Validation of Top Slot
Given a live scheduling poll with a designated top-ranked slot And required-attendee calendars, work windows, buffer rules, and existing holds are monitored When any relevant signal changes (new vote, calendar event change, hold conflict, work-window update) Then the system revalidates the top slot within 2 seconds And marks the slot Feasible only if all required-attendee constraints are satisfied and no buffer/hold conflicts exist And marks the slot Infeasible if any required-attendee constraint is violated And records a validation result with reason codes for pass/fail
Atomic Auto-Fallback and In-Place Message Update
Given the current top slot is marked Infeasible and the infeasibility has persisted for at least 10 seconds And the minimum dwell time since the last top-slot change is at least 120 seconds When a compliant candidate slot with the highest rank exists Then promote that slot atomically as the new top And update the Slack/Teams message in place (same message/thread ID) with the new ordering and progress bar And do not create a new thread or additional message And apply calendar hold/invite updates within the same transaction And complete end-to-end update within 5 seconds p95 And ensure no client observes an intermediate/partial state
Hysteresis and Minimum Dwell Time
Given potential oscillations in feasibility or ranking When evaluating whether to swap the top slot Then require infeasibility to persist for at least 10 seconds before considering a swap And enforce a minimum dwell time of 120 seconds between consecutive top-slot changes And limit to at most 2 top-slot swaps within any rolling 10-minute window And only switch between two feasible slots if the new slot’s composite score exceeds the current by at least 10%
Consistent Update of Provisional Holds and Invites
Given a top-slot swap is approved When applying calendar changes Then remove all provisional holds/invites for the demoted slot for all participants And create provisional holds/invites for the promoted slot for all required attendees and designated optional attendees And ensure the meeting series/instance identifiers remain consistent and correct in ICS and calendar metadata And guarantee no participant ends up with both old and new holds simultaneously And if any calendar operation fails, roll back the entire swap and restore the previous state idempotently
Rollback on Feasibility Restoration
Given a previous top slot was demoted due to infeasibility And within 10 minutes its feasibility is restored And it outranks the current top slot by composite score When rollback conditions also satisfy dwell/hysteresis constraints Then atomically restore the prior slot to top And update the in-place message and calendar holds/invites accordingly within 5 seconds p95 And create an audit record linking the rollback to the original demotion event And ensure no duplicate notifications or new threads are created
Transactional Integrity and Concurrency Safety
Given concurrent events (votes, calendar updates, hold operations) may occur When performing validation, swap, or rollback Then wrap all state changes in a single transaction with a unique correlation ID And ensure operations are idempotent under retries And use optimistic locking or equivalent so only one swap can commit per generation/version And on any partial failure, revert to the last committed consistent state and surface a retryable internal error And prevent clients from observing mixed versions of state
Auditability of Feasibility Decisions and Swaps
Given any validation, swap, or rollback action completes When writing audit data Then persist an immutable audit record with timestamp (UTC), actor, action type, prior/new top slot IDs, reason codes, criteria results, dwell/hysteresis metrics, message ID, calendar operation IDs, and correlation ID And make audit records queryable by schedule ID, message ID, correlation ID, and time range And redact protected fields (e.g., emails) while preserving participant roles (required/optional) And retrieve up to 100 audit records within 200 ms p95
Live Progress Bar in Chat
"As a participant, I want a simple visual indicator of decision progress and viability so that I know whether my response is still needed and how close we are to finalizing."
Description

Render a live, in-message progress bar in Slack/Teams showing required and optional response progress, quorum attainment, and decision confidence. Update the original message in place using platform-native blocks/cards with accessible labels and color contrast. Provide graceful text-only fallbacks for clients that don’t support rich blocks. Lock and visually indicate finalization state when a slot is confirmed. Localize labels and support tenant-level theming.

Acceptance Criteria
Real-Time In-Place Progress Updates
Given a live poll message is posted in Slack or Teams When any participant (required or optional) submits or changes a response Then the original message is edited in place (same message/thread ID) within 2 seconds for 95% of events And the progress bar displays separate required and optional completion percentages and counts (e.g., 7/10 required, 12/18 optional) And the displayed counts exactly match the backend tally at the time of update And no new message is posted to the channel as part of the update
Quorum and Confidence Display
Given a meeting quorum rule exists for required attendees When the number of required responses reaches the configured quorum threshold Then the progress bar shows a 'Quorum reached' state with a success color, check icon, and an accessible text label And before quorum, the bar shows 'X/Y required responded' and no 'Quorum reached' label And decision confidence is displayed as a 0–100% integer with the label 'Confidence' and updates in place when the model output changes
Accessible Labels and Contrast Compliance
Given the progress bar is rendered with platform-native blocks/cards Then all status information (required count, optional count, quorum state, confidence) is present in text, not color-only And text and essential graphical elements meet WCAG 2.1 AA contrast (>= 4.5:1 for text against its background) And interactive elements (if any) include descriptive text labels readable by screen readers in Slack and Teams And high-contrast themes in Teams and dark mode in Slack show no loss of legibility or status information
Text-Only Fallback for Non-Rich Clients
Given a client that cannot render rich blocks/cards or when a message is delivered via a plain-text transport When the message is viewed or updated Then a text-only version is shown containing required and optional counts, quorum status, decision confidence, and finalization state And the text-only version contains no broken placeholders or unsupported markdown And the same message is edited in place where the platform supports edits; otherwise, only one concise update line is appended per change window (max once per minute)
Finalization Lock and Visual Indicator
Given a time slot is confirmed (manually or automatically) When the message updates Then the progress bar switches to a finalized state with a lock icon and the label 'Finalized' And all interactive voting components are disabled or removed And no further progress or confidence updates occur after finalization And the message footer shows a finalized timestamp
Localization and Tenant-Level Theming
Given a tenant locale is configured (e.g., fr-FR) and a custom theme palette is set When a live progress bar message is created and updated Then all labels and messages are localized with correct pluralization and numeric/percent formatting for the locale And if a translation key is missing, the English fallback is used without rendering errors And theme colors are applied to the progress bar and accents while maintaining required contrast thresholds
Notification Throttling & Thread Hygiene
"As a team, we want reordering and status changes to happen quietly in the original message so that our channel stays readable and free of spam."
Description

Debounce and batch rapid-fire updates so reorders and progress changes edit the existing message instead of posting new ones. Respect quiet hours and avoid unnecessary mentions; only escalate to organizers on tie-break or policy conflict. Guarantee thread continuity with stable message IDs per poll and rate-limit updates to platform-specific thresholds. Provide admin controls for update frequency and escalation rules.

Acceptance Criteria
Debounce Burst Updates to a Single Threaded Message
Given a live poll exists in Slack or Teams and three or more votes arrive within the configured debounceWindowMs When the system processes these votes Then it performs a single edit to the existing poll message in the same thread and posts no new message And all vote changes within the debounce window are reflected in the reprioritized options and progress bar And consecutive edits are spaced at least minEditIntervalMs apart per platform And the total number of edits in any rolling 5-minute window does not exceed the configured per-platform threshold
Stable Message ID Per Poll
Given a poll has been created and its platform messageId is stored When any reorder or progress update occurs Then the system edits the original message using the stored messageId And the root messageId remains unchanged for the lifetime of the poll And all replies continue to appear in the same thread as the original message
Quiet Hours Suppression and Deferral
Given a participant or workspace is currently within configured quiet hours (based on local timezone) When a rerank or progress update occurs Then the system performs a silent edit with no @mentions or DMs to those users in quiet hours And any pending escalations targeting those users are queued and delivered after quiet hours end And an audit log entry is recorded with pollId, userId(s), and reason "quiet_hours"
Organizer Escalation Only on Tie-break or Policy Conflict
Given a live poll requires a tie-break decision or a scheduling policy conflict is detected When the condition is first detected within a 10-minute window Then exactly one escalation DM is sent to the poll organizers and no escalation is sent for non-critical updates And the escalation respects organizer quiet hours unless overrideEscalationOutsideQuietHours is enabled And the escalation includes pollId, reason, and an action link to resolve
Admin Controls for Throttling and Escalation
Given an admin updates throttling settings (debounceWindowMs, batchMax, minEditIntervalMs per platform) and escalation rules (recipients, triggers, quietHours) When the admin saves the configuration Then inputs are validated against allowed ranges and required fields And changes propagate to the running system within 2 minutes without restart And a test mode can simulate a 20-vote burst and returns a report showing edit count, spacing, and any suppressed notifications without posting to production channels And the change is audit-logged with adminId, oldValue, newValue, and timestamp
Rate-limit Compliance and Error Handling
Given the platform API responds with rate limit (429) or transient errors during an edit When the system retries the update Then it honors Retry-After headers where present and uses exponential backoff with jitter up to maxRetries And it does not create a new message while the original messageId is valid And if maxRetries are exhausted, live edits for that poll are paused and an admin console alert is raised with pollId and error summary And average edits per minute per poll over a rolling 5-minute window remain ≤ the configured per-platform threshold
Suppress Mentions on Routine Updates
Given a rerank or progress change does not require user action (no tie-break, no policy conflict) When the system updates the thread Then the edit is posted silently without @here, @channel, or individual mentions And mention count for the poll over 24 hours equals the number of action-required events only And a user receives at most one notification per action-required event
Explainability & Audit Trail
"As an organizer, I want to understand why a slot is on top and how recent votes changed the order so that I can justify the decision and adjust settings if needed."
Description

Attach a "Why this order?" action to the poll that reveals the factors driving the current ranking (required coverage, predicted attendance, fairness rotation) and the effect of the most recent votes. Persist an immutable event log of votes, recalculations, and slot promotions with timestamps and actor IDs. Enforce data minimization, redactions, and RBAC. Provide export within retention windows for compliance and organizer review.

Acceptance Criteria
Why This Order Panel Shows Factor Breakdown and Recent Vote Impact
Given a live poll message in Slack or Teams with the "Why this order?" action enabled When an authorized participant invokes the action Then a modal opens within 2 seconds showing for the top 3 ranked options: required coverage %, predicted attendance %, fairness rotation score, and overall score with weightings And the modal includes a "Most recent change" section summarizing the latest vote's effect on scores and rank positions without revealing voter identity And the displayed scores match the current backend ranking data to within ±0.1% for percentages and exact integers for counts
Immutable Event Log Captures Votes, Recalculations, and Promotions
Given a poll receives votes, unvotes, and reranks options When the audit log API is queried for that poll Then each event entry includes: ISO-8601 UTC timestamp, tenant-scoped pseudonymous actorId, eventType (vote_cast, vote_retracted, recalculated, slot_promoted, slot_demoted), affectedOptionIds, preRank and postRank snapshots, and reason/code where applicable And event entries are append-only; attempts to update or delete an entry return HTTP 409 and do not change existing entries And sequence numbers are strictly increasing per poll with no gaps for successful writes
Role-Based Access Control for Explanations and Audit
Given RBAC roles Organizer, Required, Optional, and External are assigned When users access "Why this order?" or the audit log/export Then Organizers can view full factor breakdown and export; Required/Optional can view factor breakdown for their poll with redacted identities; External users receive 403 with no data returned And all access attempts are logged with actorId, resource, and outcome
Data Minimization and Redaction Policy Enforcement
Given data minimization is enabled per workspace policy When generating explanations and audit entries Then personally identifiable information (names, emails, avatars) is excluded; only role, tenant-scoped actorId, and counts are included And "Most recent change" shows role-based counts (e.g., +1 Required, -1 Optional) and delta scores without listing individuals And upon a governance redaction request, a new redaction event is appended and future exports replace the actorId with a redaction token while preserving event semantics
Compliance Export Within Retention Window
Given a poll with activity within the configured retention window When an Organizer requests an export Then a downloadable export is generated within 60 seconds in JSON and CSV formats containing event log entries, ranking snapshots, and factor settings And the export includes a SHA-256 checksum and metadata fields (generatedAt, pollId, retentionExpiresAt) And if the poll is outside the retention window, the request returns 410 Gone with a message referencing the retention policy
Real-Time Consistency Across UI, Explanation, and Audit
Given a new vote arrives that changes the ranking When the poll UI updates the option order without creating a new thread Then within 1 second the "Why this order?" modal reflects the updated scores and ranks, and the audit log contains a recalculation event and any slot promotion/demotion event with a matching postRank snapshot And the progress bar reflects the latest tallies consistent with the audit event
Consistency & Concurrency Controls
"As a participant, I want everyone to see the same current ordering in real time so that we can coordinate decisions without confusion."
Description

Guarantee a single source of truth for poll state under concurrent votes via optimistic concurrency control and versioned state updates. Handle out-of-order and duplicated events idempotently. Ensure end-to-end update latency targets (p95 <1.5s, p99 <3s) with health metrics, alerts, and backpressure. Provide chaos and race-condition tests to validate ordering consistency across clients.

Acceptance Criteria
Optimistic Concurrency Control on Poll Updates
- All poll update requests include a required version field; requests missing it are rejected with 400. - Stale updates (version < current) are rejected with 409 Conflict and do not mutate state; response includes the currentVersion. - Successful updates atomically increment version by 1 and append an audit entry with updateId, priorVersion, and timestamp. - In a soak test with 50 concurrent updaters for 10 minutes, >=99.99% of updates succeed after automatic retry; no lost updates detected by event replay. - Final poll state equals the fold of all unique events exactly once (checksum matches canonical replay).
Idempotent Handling of Duplicated and Out-of-Order Events
- Each vote event carries a globally unique eventId and a monotonic per-voter sequence number. - Duplicate events (same eventId) within a 24h dedup window produce no additional side effects; dedup_hits metric increments. - Out-of-order events within a 10s reorder window are resequenced; beyond the window they are applied via a commutative merge to yield the same final state as sorted replay. - Property-based tests over 1,000,000 events with random duplication and reordering report 0 state divergences versus canonical sorted processing. - Exactly-once effects are maintained despite at-least-once delivery; no double-counted votes observed in audit or metrics.
Consistent Live Rerank Across Clients
- After a committed update, 95% of subscribed clients display the new ranking within 1.5s and 99% within 3s (end-to-end measured). - For any given version, all active clients show identical top-3 options and selected fallback slot within 500ms of each other. - If the top choice becomes infeasible, the system promotes the next compliant slot without posting a new thread/message; the original message is edited in place. - Client UIs enforce monotonic version application; no client displays version N-1 after having displayed version N. - Progress bar values match server tallies for the displayed version; a staleness indicator appears if data age exceeds 5s.
Latency SLOs and Backpressure Under Load
- Under a load of 1,500 votes/min with a realistic required/optional mix, end-to-end update latency meets p95 <= 1.5s and p99 <= 3s for 30 consecutive minutes. - When inbound rate exceeds 2,000 votes/min for >60s or queue delay >3s, backpressure engages prioritizing required voters; optional voters may receive 429 with Retry-After or delayed prompts. - Queue depth remains bounded (<= 5,000 messages) and recovers to <1,000 within 2 minutes post-surge. - No data loss under backpressure; final state equals the set of unique events processed; error budget burn does not exceed 2x over any 1-hour window.
Observability, Health Metrics, and Alerts
- Metrics emitted: update_latency_ms (p50/p95/p99), queue_depth, version_conflicts_count, dedup_hits, reorder_buffer_ms, client_fanout_lag_ms, backpressure_active_flag. - Dashboards provide per-poll and per-integration (Slack/Teams) breakdowns; metrics latency <=60s. - Alerts trigger within 2 minutes when p95 update_latency_ms >1.5s for 5 consecutive minutes, p99 >3s for 5 minutes, or version_conflicts_count >2% over 5 minutes. - Runbooks linked from alerts contain mitigation steps; median time-to-first-action <=5 minutes. - /health returns OK only when dependencies meet thresholds and includes a latestVersion timestamp that is strictly increasing.
Chaos and Race-Condition Validation
- Chaos tests introduce partitions, duplicate deliveries (2x), random delays (0–3s), and clock skew (+/-200ms) across services for 100 runs. - 0 incorrect final states versus canonical sorted replay; 0 crashes; no deadlocks/livelocks observed. - During chaos, 99.9% of updates are eventually delivered to clients within 30s; monotonic version ordering preserved (no regressions in client logs). - Race test with 100 concurrent updaters on the same poll yields 0 lost updates; 409 conflicts resolved via retry with success rate >=99.99%. - CI gates the main branch on these tests; any violation fails the pipeline.

Threaded Reminders

Keep everything in one lightweight conversation. The initial nudge, gentle reminders, and the final confirmation all stay in a single Slack/Teams thread. Reminders respect quiet hours and only ping non‑responders, with snooze and ‘I’m flexible’ quick actions. Noise drops, response rates rise, and everyone knows where to look for the outcome.

Requirements

Unified Thread Lifecycle Management
"As a remote team lead, I want all reminder messages kept in one thread so that everyone can find context and outcomes in a single place."
Description

Anchor all scheduling nudges, reminders, and the final confirmation in a single Slack/Teams thread tied to a specific meeting proposal. The system creates or locates the canonical thread, persists thread identifiers (Slack thread_ts, Teams replyToId) against the TimeTether meeting candidate, and posts all subsequent updates as replies. It supports thread reopening when proposals change, handles deleted or archived channels by recreating the thread with a backlink, and updates message headers to reflect current proposal status. The outcome is a single source of truth per decision, lower channel noise, and a consistent place for participants to find context.

Acceptance Criteria
Create or Locate Canonical Thread (Slack/Teams)
Given a meeting candidate with a designated Slack channel or Teams chat When the initial scheduling nudge is triggered Then the system locates an existing canonical thread via persisted identifier and posts the nudge as a reply And if no canonical thread exists, the system creates a single new parent message and persists its thread identifier (Slack thread_ts or Teams replyToId) And at most one new parent message is created per meeting candidate And the parent message contains the proposal title, candidate times summary, and quick actions
Persist Thread Identifiers on Meeting Candidate
Given a new or located canonical thread for a meeting candidate When the thread is established or confirmed reachable Then the system saves platform, channel/chat ID, parent message ID, thread identifier (thread_ts/replyToId), thread URL, and lastUpdatedAt on the meeting candidate record And the persisted identifiers can be retrieved via API and used to post subsequent replies without additional lookup And identifiers persist across service restarts and retries And an audit log entry is created with who/when/where for the thread linkage
Post Updates as Thread Replies with No Duplicates
Given reminders, updates, or final confirmation events for a meeting candidate When messages are sent to participants Then each message is posted as a reply within the canonical thread (not as a new root message) And a deduplication key prevents duplicate replies for the same event upon retries And the number of root messages for the meeting candidate remains exactly one And each reply includes the current proposal status and timestamp
Reopen Thread and Update Headers on Proposal Change
Given an existing canonical thread for a proposal When the proposal’s time options or status change (e.g., new times added, option withdrawn, decision made) Then the parent message header is edited to reflect the current proposal status and last updated timestamp And a new reply summarizes the changes and provides updated quick actions And obsolete interactive elements in prior messages are disabled or marked superseded And the same canonical thread remains in use (no new threads created)
Recover from Deleted/Archived Channel with Backlink
Given a canonical thread whose channel/chat is deleted, archived, or the bot loses posting permission When the system attempts to post and receives a definitive failure indicating the thread is unreachable Then the system creates a replacement canonical thread in the configured fallback location (e.g., organizer DM or fallback channel) And the first message includes a backlink to the prior thread (if resolvable) and a brief reason for recreation And the meeting candidate’s persisted identifiers are updated to the new thread And participants are notified in-thread that conversation continuity has moved
Targeted, Quiet-Hours-Respecting Reminders in Thread
Given a canonical thread and participant quiet hours/timezones When sending reminder replies Then reminders are scheduled or delayed to avoid each participant’s quiet hours And only non-responders are mentioned in the reminder reply And snooze and “I’m flexible” quick actions are present in the reply And all reminders are posted as thread replies, not as new channel messages
Quiet Hours and Work Window Compliance
"As a distributed teammate, I want reminders to respect my quiet hours so that I am not pinged outside my work time."
Description

Schedule and send reminders only within each participant’s allowed work windows and org-configured quiet hours across time zones. Integrate with TimeTether’s availability model to compute per-user send windows, apply daylight saving rules, avoid weekends and holidays as configured, and queue messages for the next permissible window. Provide per-user overrides and team-level policies, with safeguards to prevent burst pings by pacing messages. This reduces after-hours interruptions and aligns reminders with the fairness goals of TimeTether.

Acceptance Criteria
Enforce Per-User Quiet Hours and Work Windows Across Time Zones
Given a participant with timezone America/Los_Angeles and work window 09:00–17:00 local, and org quiet hours 18:00–08:00 local When a reminder becomes due at 07:30 local Then the reminder is queued and not sent until 09:00 local within the same business day. Given the same participant When a reminder becomes due at 16:55 local Then the reminder is sent immediately and never later than 17:00 local. Given any participant When the work window and quiet hours overlap or conflict Then the effective send window equals the intersection of the user’s work window and the non-quiet hours for that locale.
Weekend and Holiday Avoidance With Regional Calendars
Given org policy "avoid weekends" is enabled and the participant’s locale is en-US When the next permissible window falls on Saturday or Sunday Then the reminder is queued to Monday at 09:00 local or the start of the user’s next work window, whichever is later. Given an org holiday calendar that marks 2025-11-27 as a holiday for the participant’s locale When the next permissible window would be on 2025-11-27 Then the reminder is queued to the next non-holiday workday at the start of the user’s work window. Given a participant assigned to locale en-IN and multi-region calendars are enabled When a date is a holiday in the participant’s locale but not the organizer’s Then that participant is not pinged on that date.
Daylight Saving Time Transitions Are Handled Correctly
Given a participant in Europe/Berlin where clocks move forward at 02:00 to 03:00 When a reminder is scheduled for 02:30 local on the transition day Then it is sent at 03:00 local and not duplicated. Given a participant in America/New_York where clocks move back at 02:00 to 01:00 When a reminder is scheduled for 01:30 local on the transition day Then it is sent once during the first 01:30 occurrence and not sent again during the repeated hour. Given any DST change When computing permissible windows Then all comparisons use the participant’s local time with the correct offset for that timestamp as provided by the timezone database.
Per-User Overrides and Team-Level Policy Precedence
Given a team policy work window 09:00–17:00 and a user override 10:00–16:00 When computing the user’s permissible send window Then the system uses 10:00–16:00. Given a team policy allowing 08:00–18:00 and an org quiet hours rule 19:00–07:59 When a user override expands to 07:30–18:30 Then the effective window remains 08:00–18:00 (no sends outside org quiet hours or team defaults). Given policy precedence defined as Org Quiet Hours > User Overrides > Team Defaults When conflicting settings exist Then the effective send window follows that precedence with no sends outside the resolved window.
Anti-Burst Pacing Within Permissible Windows
Given a team with 40 participants whose next permissible window opens at 09:00 local simultaneously When reminders become eligible at 09:00 Then the system sends at a rate no greater than 8 messages per minute per team and no more than 1 message per participant per 60 seconds. Given the same batch When the pacing would push some sends past the end of a participant’s permissible window Then the remaining reminders are deferred to the next permissible window instead of being sent late. Given pacing is applied When multiple reminders target the same participant within a 10-minute interval Then they are coalesced into a single message where supported (Slack/Teams) respecting the same window and pacing rules.
Non-Responders Only and Snooze Respect Quiet Hours
Given a thread with participants A (responded), B (snoozed for 3 hours), and C (no response) When the permissible window opens for all at 10:00 local Then only C is pinged at or after 10:00 local. Given participant B’s snooze ends at 12:15 local during quiet hours When the next permissible window starts at 13:00 local Then B is pinged at or after 13:00 local. Given a participant marks “I’m flexible” When computing subsequent reminders for the same scheduling round Then that participant is excluded from further pings and receives no messages during quiet hours.
Non-Responder Targeting and Smart Deduplication
"As a participant, I want only non-responders to be pinged so that I am not notified after I have already acted."
Description

Target reminder mentions exclusively to participants who have not acted, based on real-time response state captured from thread interactions and calendar responses. Maintain a per-user state machine (pending, accepted, flexible, snoozed, declined) and build mention lists accordingly. Deduplicate across Slack and Teams by resolving identities to a single TimeTether user, and suppress reminders once action is detected, even if received on another platform. Avoid re-notifying users within a configurable cooldown, and annotate the thread with who’s still outstanding. This minimizes noise and improves response rates.

Acceptance Criteria
Target Mentions to Non-Responders
Given a meeting thread with participants in states pending, accepted, flexible, snoozed, and declined When a reminder is sent Then only users whose current state is pending are @mentioned, and zero users in accepted, flexible, snoozed, or declined are @mentioned And the number of @mentions equals the count of pending users at send time And real-time state from thread interactions and calendar responses is used to build the mention list at the moment of dispatch Given a user transitions from pending to accepted within 2 seconds before dispatch When the reminder is processed Then the user is not @mentioned
Real-Time State Machine Updates
Given a user clicks Accept in the thread or responds Accepted via calendar When the action is received Then the user’s state transitions to accepted within 3 seconds and the user is excluded from future mention lists Given a user clicks I’m flexible quick action When the action is received Then the user’s state transitions to flexible and the user is excluded from future mention lists Given a user clicks Snooze for X minutes When the action is received Then the user’s state transitions to snoozed with an expiry timestamp and the user is excluded from mentions until expiry; upon expiry the state auto-transitions to pending Given a user clicks Decline or responds Declined via calendar When the action is received Then the user’s state transitions to declined and the user is excluded from future mentions Given duplicate or repeated actions When processed Then the latest valid action determines the state and processing is idempotent
Cross-Platform Identity Deduplication and Suppression
Given Slack user A and Teams user B resolve to the same TimeTether user identity When building a reminder cycle Then at most one @mention is issued across both platforms for that TimeTether user And if the user acts on either platform or via calendar Then reminders and mentions are suppressed on both platforms within 5 seconds of detection Given two accounts do not resolve to the same TimeTether user When building a reminder cycle Then mentions are treated independently per distinct identity
Configurable Cooldown Between Reminders
Given a per-user cooldown of N minutes is configured for the thread When preparing to @mention a pending user Then the system checks the user’s last reminder-mention timestamp shared across Slack and Teams for this thread and only @mentions if at least N minutes have elapsed And if fewer than N minutes have elapsed Then the user is skipped for this cycle without resetting the cooldown Given the cooldown expires When the next reminder cycle runs Then the user may be @mentioned if still pending
Quiet Hours Respect for Non-Responder Reminders
Given a pending user is within their configured quiet hours When a reminder is sent Then the user is not @mentioned and their reminder is deferred until the next work window start And skipping due to quiet hours does not reset or start the per-user cooldown When the user’s work window opens Then the next reminder may @mention the user if still pending and cooldown permits
Thread Annotation of Outstanding Participants
Given a reminder is sent or a participant’s state changes When processing completes Then a single Outstanding summary message in the same thread is posted or updated within 5 seconds to show the count and display names of users still in pending And users in accepted, flexible, snoozed (until expiry), or declined are not listed And if zero users remain pending Then an All set confirmation is posted and no further reminders are sent for this thread
Snooze and “I’m Flexible” Quick Actions
"As a busy engineer, I want to snooze or mark myself flexible with one click so that I can acknowledge the request without being interrupted."
Description

Offer interactive quick actions within the thread to snooze reminders (for example, 1 hour or next workday) or mark “I’m flexible,” updating the backend immediately. Implement Slack Block Kit buttons and Microsoft Teams Adaptive Cards with secure callback endpoints, persist selections, and reflect the change in-thread with an updated status indicator. Snoozed users are excluded until the snooze expires; flexible users reduce their reminder frequency and feed TimeTether’s fairness-driven rotation to distribute meeting burdens. The result is faster user input with fewer interruptions.

Acceptance Criteria
Slack: Quick Actions in Thread
- Given a Slack thread with a TimeTether reminder message using Block Kit When a targeted user views the message during their work window Then Snooze (1h, Next Workday) and I'm Flexible buttons are visible and enabled - Given the same thread When the user clicks Snooze 1h Then the backend persists a snooze with expiry = click_time + 1 hour (in the user's timezone) and responds 200 within 2 seconds And the thread message updates within 3 seconds to show "Snoozed until <local time>" - Given the same thread When the user clicks Snooze Next Workday Then the backend persists a snooze ending at the next start of the user's work window on the next workday and responds 200 within 2 seconds And the thread message updates within 3 seconds to show "Snoozed until next workday <local date/time>" - Given the same thread When the user clicks I'm Flexible Then the backend persists the flexible flag immediately and responds 200 within 2 seconds And the thread message updates within 3 seconds to show "Marked flexible" - Given duplicate click payloads arrive within 5 seconds When the callbacks are processed Then only one state change is persisted and subsequent duplicates are acknowledged without additional writes - Given an acted-on message When the same user attempts the same action again Then the system returns 200 and performs no additional state change
Microsoft Teams: Quick Actions in Thread
- Given a Teams channel or chat thread with a TimeTether reminder Adaptive Card When a targeted user views the card during their work window Then the card renders Snooze (1h, Next Workday) and I'm Flexible actions - Given the same thread When the user clicks Snooze 1h or Next Workday Then the backend persists the correct snooze and responds 200 within 2 seconds And the card refreshes within 3 seconds to display the user's new status and snooze end time in their local timezone - Given the same thread When the user clicks I'm Flexible Then the backend persists the flexible flag and responds 200 within 2 seconds And the card refreshes within 3 seconds to display "Marked flexible" - Given concurrent actions by multiple users When the card is refreshed Then all users' latest statuses are visible without loss or duplication
Secure Callback Verification and Idempotency
- Given a Slack interactive callback request When the endpoint receives the request Then it verifies X-Slack-Signature using the signing secret and X-Slack-Request-Timestamp within a 5-minute tolerance and rejects failures with HTTP 401 and no persistence - Given a Microsoft Teams Adaptive Card action request When the endpoint receives the request Then it validates the Microsoft JWT/appId and rejects failures with HTTP 401 and no persistence - Given any callback request When the same action_id/user_id/message_ts combination is received more than once Then the endpoint behaves idempotently (one state change, subsequent requests return 200 with no new writes) - Given normal load (p50) When processing a valid callback Then end-to-end response time is <= 2 seconds and audit logs capture user_id, action, thread_id, correlation_id
Snooze Persistence and Reminder Suppression
- Given a user snoozes for 1 hour When the scheduler computes the next reminder batch for the related thread Then that user is excluded from targeting until the snooze expiry timestamp - Given a user snoozes until next workday When the user's timezone and work window are applied Then the snooze expiry is set to the next workday's work window start and the user is excluded until that time - Given a snooze expires during the user's quiet hours When the next reminder batch runs Then the user is not pinged during quiet hours and is eligible starting at the next work window start - Given multiple active threads for the same meeting When reminders are sent Then the user's snooze applies across all associated reminders for that meeting - Given the user had not responded prior to snoozing When the next reminder wave is sent to non-responders Then the snoozed user is not pinged
I'm Flexible Frequency Reduction and Fairness Integration
- Given a user marks I'm Flexible When subsequent reminder waves are scheduled for the same meeting during the flag's active period Then the user receives at most 50% of the reminder pings they would otherwise receive, excluding periods when the user is snoozed or in quiet hours - Given a user marks I'm Flexible When the fairness-driven rotation score is computed Then the user's selection weight is reduced by an applied multiplier <= 0.8 for the flag's active period - Given the flag is active When backend state is queried Then the flexible flag, activation timestamp, and configured duration are persisted and retrievable - Given the flag's configured duration elapses When the next scheduling/notification cycle runs Then the flexible flag automatically expires and normal reminder frequency and rotation weighting resume
In-Thread Status Indicator and Visibility
- Given a user takes a quick action When the system processes the action Then the Slack message or Teams card is updated within 3 seconds to include a per-user status line showing the user's display name, action (Snoozed until <time> or Marked flexible), and a timestamp in the user's timezone - Given any participant opens the thread When the thread content loads Then they see the current status indicators for all acted-on users without exposing their quiet-hour schedule details - Given simultaneous updates from multiple users within 2 seconds When the thread content is refreshed Then all statuses are present with correct latest values and no interleaving conflicts - Given a user has acted When the next reminder wave is prepared Then the system treats that user as a responder for the purpose of non-responder targeting (snoozed users excluded; flexible users targeted at reduced frequency)
Final Confirmation and Thread Summary
"As an organizer, I want a final confirmation posted in the thread so that the outcome is clear and calendars are updated."
Description

Post a definitive confirmation message in the thread once consensus is reached, summarizing the chosen time, attendees, and time zone normalization, with one-click calendar add and a link to the TimeTether schedule. Automatically stop further reminders on closure, handle late joiners by posting an informative update, and ensure idempotent confirmation in case of retries. Sync invitations via connected calendars and update the meeting record with thread permalinks. Participants know exactly where to find the outcome.

Acceptance Criteria
Consensus Reached — Thread Confirmation Posted
Given a scheduling thread has reached consensus on date/time and final attendees When the system marks the meeting as confirmed Then a single confirmation message is posted in the same thread within 60 seconds containing: the chosen date/time in a canonical timezone, each attendee’s localized time (or a per-attendee local time list), the final attendee list (required/optional), a one-click “Add to Calendar” action, and a “View in TimeTether” schedule link And the confirmation message includes the meeting title and duration and is labeled "Confirmed" And no other non-confirmation message appears after it from the app until any late-joiner update is posted
Closure Stops Reminder Cadence
Given reminder jobs are scheduled for the thread When the confirmation is posted (meeting marked confirmed) Then all pending reminder jobs for that thread are canceled within 30 seconds And no additional reminder messages are sent in that thread after the confirmation timestamp And the thread state is recorded as "closed" for reminders
Late Joiner Responds After Confirmation
Given the meeting is confirmed and reminders are stopped And a participant who did not respond interacts in the thread (message or quick action) after confirmation When the system processes the late response Then an informative update is posted in the thread addressing the participant within 60 seconds, restating the confirmed time with their local time and the attendee list status And the update offers one-click options to "Add me to invite" or "Decline" (if applicable) And the confirmation message remains unchanged and linked from the update And the reminder cadence does not restart and the confirmed time is not altered
Idempotent Confirmation on Retry
Given duplicate webhook deliveries or job retries occur for the same thread confirmation operation When the system processes a retry with the same idempotency key/operation identifier Then exactly one confirmation message exists in the thread (no duplicates) And only one calendar event/invite per attendee exists (no duplicates sent) And the meeting state remains "confirmed" with a single canonical event ID And the operation logs record that the retry was deduplicated
Calendar Sync and Event Enrichment
Given attendees may have connected calendars (Google/Microsoft) or none When the meeting is confirmed Then for each connected attendee, an event invite is created or updated within 60 seconds with correct start/end, title, attendees (required/optional), and description including the thread permalink and the TimeTether schedule link And for attendees without a connected calendar, the confirmation’s one-click button provides an ICS or provider deep link that pre-populates the same event details And the time zone normalization in the event matches the time shown in the confirmation message
Meeting Record Updated With Thread Permalink
Given confirmation is posted in Slack/Teams When the system finalizes the confirmation Then the TimeTether meeting record is updated within 30 seconds to include: platform (Slack/Teams), channel, thread permalink URL, confirmation message ID, and calendar event ID(s) And the meeting record API returns these fields on retrieval And subsequent system actions (e.g., late-joiner updates) reference the stored thread permalink to post in the same thread
Admin Policy Controls and Audit Log
"As an admin, I want to set reminder policies and review logs so that we maintain compliance and reduce noise across the org."
Description

Provide org-level controls for reminder cadence, maximum pings, quiet hours policy, snooze options, and whether flexible responses suppress future reminders. Expose a settings UI and API, enforce policies at send time, and log all reminder events (who was pinged, when, why) with correlation to the thread and meeting ID. Offer exportable audit trails and metrics for compliance and optimization. This enables governance, transparency, and tunable behavior across teams.

Acceptance Criteria
Org Admin Configures Reminder Policy via UI
Given user is authenticated as Org Admin, When they open Settings > Threaded Reminders > Policy, Then the form shows fields: reminder_cadence, max_pings_per_participant, quiet_hours_windows, snooze_options, suppress_on_flexible. Given the policy form is visible, When values are out of range (cadence < 15m or > 72h; max_pings < 1 or > 5; quiet_hours window end <= start; snooze_options list empty), Then Save is disabled and field errors are displayed. Given valid values, When Save is clicked, Then the policy persists and confirmation toast appears within 2s. Given policy was saved, When the page is reloaded, Then the saved values are reloaded exactly. Given non-admin access, When the Policy page is requested, Then access is denied and no fields are editable.
Policy Management API
Given a valid admin token, When GET /api/v1/orgs/{org_id}/reminder-policy is called, Then return 200 with policy JSON containing: reminder_cadence, max_pings_per_participant, quiet_hours_windows, snooze_options, suppress_on_flexible, version, updated_at, etag. Given a valid admin token and valid body, When PUT /api/v1/orgs/{org_id}/reminder-policy with If-Match: <etag> is called, Then return 200 with updated policy, incremented version, and new etag. Given invalid token or non-admin role, When GET or PUT is called, Then return 401 or 403. Given invalid body values, When PUT is called, Then return 422 with field errors. Given ETag mismatch, When PUT is called, Then return 412 Precondition Failed.
Enforcement of Max Pings and Non-Responder Targeting
Given max_pings_per_participant = N, When evaluating recipients for meeting M, Then no participant is sent more than N pings in thread T for M. Given a participant has responded (accept/decline/confirmed availability), When the next reminder is sent, Then that participant is excluded. Given all participants have responded or max attempts reached, When evaluation runs, Then a final confirmation message is posted and no further pings are sent. Given a send is evaluated, When policy version v is in effect, Then all decisions for that send use policy snapshot v.
Quiet Hours and Snooze Enforcement
Given org quiet_hours_windows are configured, When a reminder would be sent to participant P during P's local quiet hours, Then delivery to P is deferred to the next allowed minute after quiet hours end. Given recipients in multiple timezones, When a send is executed, Then each recipient's delivery respects their own local quiet hours independently. Given participant P snoozes for duration X allowed by policy at time t, When evaluation occurs before t+X, Then P is excluded; When evaluation occurs at or after t+X and within allowed hours, Then P is included. Given a reminder is deferred due to quiet hours, When it is later delivered, Then cadence for subsequent reminders is calculated from the actual delivery time.
Flexible Responses Suppress Future Reminders
Given suppress_on_flexible = true, When participant P clicks "I'm flexible" for meeting M, Then P is excluded from all future reminders for M. Given suppress_on_flexible = false, When participant P clicks "I'm flexible" for meeting M, Then P remains eligible for future reminders for M. Given mixed participant responses, When recipients are calculated, Then suppression due to "I'm flexible" is applied per participant per meeting.
Audit Logging of Reminder Events
Given any reminder evaluation or delivery event occurs, When the system processes it, Then an immutable audit record is written with fields: event_id, timestamp_utc, meeting_id, thread_id, policy_version, reminder_type, actor, recipients[]. Given recipients[] in a log record, When recorded, Then each item includes: user_id, action (sent|deferred_quiet_hours|suppressed_max_pings|suppressed_flexible|snoozed), reason_code, attempt_number. Given a deferred delivery, When it is later sent, Then a new log record is created and linked by correlation_id to the original evaluation. Given the audit sink is unavailable, When a send is about to be executed, Then logging is retried up to 3 times and the send is aborted with alerting if logging cannot be recorded. Given Org Admin access, When requesting audit data via UI or API, Then only admins can retrieve records for their org.
Exportable Audit Trails and Metrics
Given an Org Admin selects a date range (<=31 days), filters (meeting_id, thread_id, user_id), and format (CSV|JSON), When they request an export in the UI, Then an export job is created and completes within 5 minutes for <=1,000,000 events with a downloadable link. Given a completed export, When the file is downloaded, Then it contains all matching audit records with required columns/fields and UTF-8 encoding. Given the metrics view is requested for a date range, When computed, Then the system returns totals for: reminders_sent, pings_suppressed_quiet_hours, pings_suppressed_flexible, pings_suppressed_max_pings, snoozes, final_confirmations. Given an admin API token, When POST /api/v1/orgs/{org_id}/reminder-audits/export is called with valid parameters, Then return 202 with job_id and a status endpoint returns 200 with download_url when ready.
Cross-Platform Parity and Fallbacks
"As a user, I want the same experience on Slack and Teams so that I don’t have to relearn workflows when switching tools."
Description

Deliver consistent threaded reminder behavior across Slack and Microsoft Teams, detecting capabilities per surface (channels, group chats, DMs) and applying graceful fallbacks where threading is limited. Normalize features such as mentions, interactive components, and permalinks; handle tenant consent and OAuth scopes; and provide DM-based threads when channel replies are unavailable, preserving a link back to the meeting context. Ensure feature parity tests and telemetry validate equivalent user experience on both platforms.

Acceptance Criteria
Channel Threading Parity and Navigability (Slack & Teams Channels)
Given a scheduled reminder targeting a Slack channel or a Microsoft Teams channel with thread support When TimeTether posts the initial nudge Then it is posted as a single parent message and all subsequent reminders and the final confirmation are replies to that parent on both platforms And no duplicate parent messages are created per meeting across retries And each message includes a permalink/deep link that opens the parent thread in the correct channel/chat on the respective platform And only non-responders are mentioned in reminder replies
Teams Group Chat Fallback to DM Thread
Given a reminder targeting a Microsoft Teams group chat without threaded replies When TimeTether prepares to post a threaded reminder Then it uses a DM-based thread per non-responder containing the initial nudge, reminders, and final confirmation And the DM includes a deep link back to the original group chat message or meeting context And no reminder messages are posted in the group chat for participants who have already responded
Capability Detection and Cached Decisions
Given any target surface (channel, group chat, DM) on Slack or Microsoft Teams When TimeTether evaluates posting strategy Then it detects support for threads, mentions, interactive components, and permalinks/deep links via platform capabilities And caches the detected capabilities per surface with a TTL of 60 minutes and invalidates the cache on permission changes And if detection fails, applies a safe fallback (DM-based thread, text-only actions) and emits a telemetry event with reason=failure
Mentions Normalization Across Platforms
Given a reminder targeting non-responders When mentions are rendered Then Slack uses <@userId> and Teams renders @DisplayName with proper entity binding so the UI shows a clickable mention for each targeted participant And no raw IDs or unresolved tokens appear in the message body And the number of rendered mentions equals the number of targeted non-responders
Interactive Actions Degrade Gracefully
Given a platform/surface that supports interactive components (Slack buttons or Teams Adaptive Cards) When a reminder is posted Then snooze and “I’m flexible” are presented as native interactive controls and clicking them updates the message state with an acknowledgement within 2 seconds Given a platform/surface that does not support those components When a reminder is posted Then the actions are presented as actionable text links or commands that trigger equivalent behavior And all action invocations are tracked with success/failure telemetry on both platforms
OAuth Scopes and Tenant Consent Handling
Given required OAuth scopes or tenant admin consent are missing on Slack or Microsoft Teams When TimeTether attempts to send a reminder Then no reminder content is posted and an admin consent flow is initiated (admin DM or consent card) without exposing meeting details When consent is granted Then the next scheduled reminder attempt for that tenant succeeds without additional configuration And telemetry records consent_missing, consent_granted, and delivery_resumed events with platform and tenant identifiers
Parity Testing, Telemetry, and Alerting
Given the cross-platform parity test matrix (platform × surface × capability) When the automated parity suite runs nightly Then the pass rate is ≥ 95% with no critical failures in core flows (threading, mentions, actions, links) And telemetry for reminders includes platform, surface, capabilities_used, fallback_applied, delivery_status, and interaction_result fields for ≥ 99% of events And an alert is triggered within 15 minutes if parity pass rate drops below threshold or fallback_applied spikes > 20% over 24h baseline

Preference Memory

The system learns from each tap. It remembers who consistently prefers earlier starts, mid‑week slots, or certain meeting lengths, folding those signals into future nudge windows without manual settings. Over time, nudges present more acceptable options up front, cutting declines and after‑hours exposures for distributed teams.

Requirements

Implicit Preference Signal Capture
"As a distributed team member, I want the system to learn from the times I accept or change so that future suggestions match my working hours without me configuring settings."
Description

Capture and normalize user interaction signals across scheduling flows (accepted suggestions, declines with reason, reschedules, chosen durations, day-of-week and start-time selections) and map them to a per-user profile keyed by org/team context and timezone. Ingest events idempotently with versioned schemas, deduplicate across devices, and persist minimal necessary fields with retention windows. Handle offline/async events, cross-organization membership, and merge strategies on account linking. Expose a streaming pipeline and storage layer that downstream models can query in near real time while enforcing rate limits and SLAs.

Acceptance Criteria
Idempotent Event Ingestion (Versioned Schemas)
Given an event with a new event_id and schema_version N When it is POSTed to the ingestion endpoint with a payload that validates against schema N Then the event is persisted exactly once and the API responds 202 with an ingestion_id Given the same event_id and identical payload are received again When ingested Then no new record is created the response is 200 with deduplicated=true and the original ingestion_id is returned Given the same event_id arrives with a higher schema_version and a canonical-payload hash equal to the stored record When ingested Then the stored record schema_version is updated without changing normalized business fields Given a payload fails validation for the declared schema_version When ingested Then the API responds 400 with a machine-readable error list and nothing is persisted Given a request missing event_id schema_version or event_timestamp When ingested Then the API responds 400 and nothing is persisted Given steady-state traffic over 15 minutes When measured Then ingestion success rate for non-4xx requests is >= 99.95%
Cross-Device Event Deduplication
Given two devices submit the same source_event_id for the same user_id org_id and event_type within a 24h window When processed Then only one normalized signal is persisted and downstream queries return a single instance Given two requests differ only by device_id but share identical idempotency keys When ingested Then they are deduplicated with dedup_reason=device_duplicate and no duplicate appears in storage or stream Given a replay batch containing previously ingested events When processed Then zero new records are created per duplicate event_id and the sampled dedup false-negative rate is < 0.1%
Preference Normalization to Org/Team/Timezone Profile
Given a user in org A team T selects a start time of 08:30 local on Monday with 45-minute duration When ingested Then the profile for (user org A team T) increments buckets start_time=08:00-09:00 day_of_week=Mon duration=45 tz=the user's timezone at event_timestamp Given a user accepts or declines with reason codes such as too_early too_late or duration_mismatch When ingested Then the corresponding preference weights are adjusted by +/-1 with an exponential decay half-life of 30 days Given a downstream model queries a profile snapshot filtered by (user org team) When executed Then it returns counts and weights from the last 90 days with P95 latency <= 150 ms and the results include no signals from other orgs or teams
Offline and Out-of-Order Event Handling
Given a client queues events offline for up to 7 days When it reconnects and submits events with original event_timestamp Then events are accepted and aggregated in event_timestamp order Given events arrive out of order within a 24h skew When processed Then aggregates reflect correct chronological ordering with no double counting and exactly-once semantics at the event_id level Given an event older than 7 days is submitted When ingested Then it is rejected with 422 stale_event and is not persisted Given a burst of 10k backfilled events for a single org within 10 minutes When processed Then no data loss occurs and backlog is drained with P95 end-to-end availability to the stream <= 5 seconds
Account Linking Merge Strategy
Given two accounts for the same person within the same org are linked When merge executes Then profiles are merged by union of events deduplicated by source_event_id with recency-weighted counts and event-level provenance is preserved Given linked accounts span different orgs When merge executes Then profiles remain partitioned per org and are not merged across org boundaries Given conflicting timezone histories across linked accounts When merged Then normalization uses per-event captured timezone at event_timestamp rather than a current-profile timezone Given an unlink is requested When executed Then profiles revert to their pre-merge state within 10 minutes with no orphaned events and provenance remains intact
Minimal Data Persistence and Retention Compliance
Given an event contains fields beyond the approved allowlist When ingested Then only allowlisted fields are persisted and extra fields are dropped before storage Given the raw event retention window of 90 days elapses When the daily purge job runs Then > 99.99% of eligible raw events are deleted within 24 hours and are no longer retrievable via queries or streams Given an aggregate retention window of 180 days When the purge job runs Then aggregates older than 180 days are deleted and downstream queries exclude them Given a user right-to-erasure request scoped to org A is submitted When executed Then all raw events and aggregates for that user in org A are deleted within 24 hours and audit logs confirm completion Given a new schema version adds fields When ingested Then only fields marked minimal_necessary=true are persisted until a data minimization review approves additional fields
Streaming Access, Rate Limits, and SLA
Given a consumer subscribes to the preference stream When a new event is ingested Then the event is available on the stream with P95 end-to-end latency <= 2 seconds and P99 <= 5 seconds Given normal operations over a rolling 30-day window When measured Then stream availability is >= 99.9% and data correctness audit pass rate is >= 99.99% Given a producer exceeds 100 writes per user per minute or 1000 writes per org per minute When requests arrive Then the API responds 429 with a Retry-After and no throttled events are persisted or emitted Given a consumer resumes from a stored offset When reconnecting Then they receive all events at-least-once with monotonic offsets and no gaps and duplicates are marked with duplicate=true
Privacy & Consent Controls
"As a privacy-conscious user, I want to control whether my preferences are stored and be able to delete them so that I feel confident using the product."
Description

Provide explicit, contextual consent for Preference Memory with clear explanations of purpose, data categories, and retention. Offer granular user controls to pause learning, clear history, and opt out per org, with admin policy defaults (opt-in/out, retention length) and regional compliance (GDPR/CCPA) including DSR workflows and secure deletion. Apply data minimization, pseudonymization at rest, encryption in transit/at rest, and audit logs for access. Document and enforce retention (e.g., 12-month rolling window) and purpose limitation across services, including APIs for admins to manage and export preference data.

Acceptance Criteria
Contextual Consent Prompt at First Nudge
Given a user triggers a Preference Memory-powered nudge for the first time in an org When the nudge UI is about to render Then a consent modal is displayed with purpose, data categories, retention period, and links to privacy policy/DSR info tailored to the user’s region And the user can Accept or Decline without losing the ability to schedule via non-learning options And no preference data is written until the user Accepts when the org default is opt-in And if the org default is opt-out, learning remains disabled immediately upon Decline And the stored consent record includes user ID, org ID, decision, timestamp (UTC), policy/version, locale/region, and surface where shown And subsequent nudges in that org do not re-prompt unless policy/version or regional basis changes
Granular User Controls for Learning and History
Given a signed-in user opens Privacy & Consent settings When the user toggles Pause Learning for an org Then new preference signals are not collected or used in nudges within 60 seconds And a banner indicates learning is paused When the user selects Clear History for an org and confirms Then all preference records for that user/org are deleted from primary stores within 60 seconds and removed from caches within 10 minutes And a non-reversible deletion receipt with request ID and timestamp is shown When the user opts out for an org Then Preference Memory stops collecting and using data for that org immediately and nudges fall back to non-learning options
Org Admin Policy Defaults and Regional Overrides
Given an org admin has the Manage Privacy permission When the admin sets default consent mode (opt-in or opt-out) and retention length (3–24 months) at org and region levels in the Admin Console or API Then the policy change propagates to all members and services within 15 minutes And affected users are re-prompted for consent if the new policy is stricter or materially different And all policy changes are recorded with actor, old/new values, timestamp, scope (org/region), and reason in an immutable audit log And the Admin API exposes GET/PUT endpoints for these policies with RBAC enforced (401/403 on unauthorized)
Data Minimization, Pseudonymization, and Encryption
Given Preference Memory persists user signals When data is written to storage Then only the minimum required fields (user/org IDs, signal type, timestamp, value, confidence) are stored; no raw calendar content or invite bodies are persisted And identifiers are pseudonymized at rest with keys managed by KMS And data is encrypted at rest (AES‑256) and in transit (TLS 1.2+) And automated tests verify only allowed fields are persisted and blocked fields are rejected with metrics logged And service accounts access data via least-privilege roles with quarterly review
Data Subject Requests (Access/Export/Delete)
Given a verified DSR is submitted by or on behalf of a user for a specific org When the request is Access/Export Then a complete, machine-readable export (JSON/CSV) of preference data and consent records is available within 30 days and downloadable via secure link expiring in 24 hours When the request is Delete Then preference data for that user/org is deleted from primary stores within 24 hours, removed from caches within 10 minutes, and flagged for non-restore in backups with expiry per backup policy And a tamper-evident audit entry records request type, actor, scope, timestamps, and outcome
Retention Enforcement: Rolling Window
Given an org retention policy (default 12 months unless overridden) When the nightly retention job runs Then all preference records older than the policy window are purged, with counts and IDs logged to an immutable audit stream And no record older than the policy window is queryable via UI or API And failures trigger alerts and are retried with exponential backoff until resolved
Access Audit Logging for Preference Data
Given any read/write/export/delete operation on preference or consent data When the operation completes Then an audit event is written including actor ID/role, subject user/org, operation, purpose, timestamp (UTC), request origin (IP/Service), correlation ID, and outcome And audit logs are append-only (WORM), queryable by admins via UI/API with filters (actor, subject, date range, operation), and retained for at least 24 months And unauthorized users cannot access audit logs (RBAC enforced with 401/403)
Preference Scoring & Decay Model
"As a team lead, I want the system to quickly pick up my recurring patterns and adapt over time so that suggestions stay relevant as my schedule changes."
Description

Transform raw interaction events into per-user preference vectors (start-time distributions by weekday, duration affinity, day-of-week likelihood) with confidence scores. Apply recency-weighted decay to adapt to changing schedules and thresholds to determine when preferences are actionable. Provide cold-start defaults using org-level norms, role heuristics, and timezone work windows. Support multi-team segmentation and context-aware models (e.g., standups vs. design reviews). Deliver a low-latency scoring service with batch backfills, feature versioning, and monitoring for drift and data quality.

Acceptance Criteria
Per-User Preference Vector Computation
Given a user with >= 20 valid interaction events within a 90-day configurable lookback and config version v1 When the batch scorer runs Then it writes a preference vector containing startTimeByWeekday distributions in 30-min bins normalized per weekday, durationAffinity over standard durations, dayOfWeekLikelihood normalized to 1, per-dimension confidence scores in [0,1], schemaVersion, and generatedAt UTC, and it is retrievable via GET /v1/preferences/{userId} Given identical input events and config version When the scorer is executed twice Then the outputs are identical for all probability and confidence values Given the user profile timezone is America/New_York When start-time distributions are computed across DST boundaries Then bucketization uses the user timezone with correct DST application
Recency-Weighted Decay Adaptation
Given decay half-life is set to 21 days and two positive signals occurred 30 days and 1 day ago When weights are computed Then weight_recent / weight_old >= 2.5 Given no qualifying events in the last 60 days When confidence is computed Then each dimension's confidence <= 0.4 and actionable=false Given 5 consecutive positive signals in the 08:00–09:00 local window during the last 7 days When the vector is recomputed Then the highest-probability start-time bin falls within 60 minutes of 08:30 and its probability increases by >= 0.20 absolute compared to the prior vector
Actionability Thresholds and Fallbacks
Given a dimension has confidence >= 0.70 and effective sample size >= 15 weighted events When scoring is executed Then actionable=true for that dimension Given a dimension has confidence < 0.70 or effective sample size < 15 When scoring is executed Then actionable=false and the response includes reasonCode="insufficient_confidence" for that dimension Given a top start-time bin lies outside the user's work window When constraints are enforced Then the bin is clipped to the nearest boundary, adjusted=true is returned, and no probability mass remains outside the work window Given the request includes actionableOnly=true When scoring is executed Then only actionable dimensions are returned in the response
Cold-Start Defaults and Blending
Given a new user with zero interactions, org defaults available, role=Design Lead, timezone=Europe/Berlin, and work window 09:00–17:00 When scoring is executed Then the start-time distribution places >= 95% probability within 09:00–17:00 local, day-of-week likelihood allocates Tue–Thu sum >= 0.60, and durationAffinity matches the org default top-2 durations Given the user's first 5 positive interactions favor 30-min meetings starting 09:30–10:30 on Wed/Thu When the vector is recomputed Then learned-signal contribution >= 50% and cold-start prior contribution <= 50% Given the user accumulates >= 10 positive interactions When the vector is recomputed Then cold-start prior contribution <= 20% Given an API call for a cold-start user When measured under nominal load Then p95 latency <= 200 ms and 5xx error rate < 0.1%
Context- and Team-Aware Segmentation
Given meetingType=standup and teamId=A When scoring is executed Then the response includes the vector scoped to (userId, teamId=A, meetingType=standup) and excludes events from other teams or meeting types Given meetingType=design_review and teamId=A When the design_review vector is recomputed Then the standup vector remains unchanged (cosine similarity delta <= 0.01) Given no meetingType is provided When scoring is executed Then the global vector is returned and reasonCode="fallback_global" is included Given the user moves from team A to team B When the membership change event is processed Then team A vectors remain unchanged and a team B vector is created or updated within 15 minutes
Scoring Service SLA, Backfill, Versioning, and Monitoring
Given online scoring traffic of 200 RPS per shard When latency and errors are measured over a 15-minute window Then p95 <= 150 ms, p99 <= 300 ms, and 5xx rate < 0.1% Given a nightly batch backfill of 1,000,000 users When the job runs Then throughput >= 10,000 users/min sustained, total duration < 2 hours, and schema errors = 0 Given request header X-Preference-Version=2 When scoring is executed Then the response contains schemaVersion=2 and computation uses config v2; and when the header is absent the default version is used Given a drift check comparing the current week's distributions to a 4-week baseline When population stability index (PSI) > 0.20 for any dimension Then an alert is emitted within 5 minutes and the dashboard displays the affected dimension and cohort Given the data freshness SLO of 24 hours for active users (>= 1 event in last 30 days) When querying vectors Then >= 99% have generatedAt within 24 hours Given identical inputs (events, config, version) When the scorer runs multiple times Then outputs are bitwise identical or within numerical tolerance 1e-6 for all probability values
Personalized Nudge Window Generation
"As a scheduler, I want nudges to surface the best times for everyone up front so that I send fewer proposals and get faster confirmations."
Description

Generate ranked nudge windows that intersect invitees’ working hours, preference vectors, and stated constraints while minimizing after-hours exposure. Respect hard policies (no after-hours, meeting length caps) and fairness rotation rules, and provide deterministic fallbacks when preferences conflict. Return the top 3–5 candidate windows with confidence scores and rationale to the UI/API within defined latency SLAs. Handle multi-timezone calculations, daylight saving transitions, and recurring series alignment. Log decisions for auditability and future learning.

Acceptance Criteria
Ranked Nudge Windows Within Working Hours
Given an invite with two or more participants each with defined working hours and stated constraints (including any "no after-hours" flags) When the system generates nudge windows Then it returns between 3 and 5 candidate windows sorted by descending score And no returned window violates any participant's hard policies And for participants with "no after-hours", each window's local time fully falls within their working hours And the top-ranked window has the minimum total after-hours minutes across all participants among all feasible candidates evaluated
Preference-Weighted Ranking and Confidence Output
Given attendees with recorded preference vectors (≥10 relevant historical events per attendee or model confidence ≥0.60) When the system generates nudge windows Then start-time, day-of-week, and meeting-length preferences are applied with non-zero weights to the scoring function And each candidate includes a confidence score in the range [0.000, 1.000] with at least 3 decimal places And each candidate includes a rationale listing the top contributing factors that account for ≥70% of the score (e.g., "early start preference", "mid-week bias", "length match") And if insufficient preference data exists, the candidate is labeled "default weights applied" and its confidence score is ≤0.500
Hard Policy Compliance and Fairness Rotation
Given hard policies (e.g., no after-hours, maximum meeting length L) and an existing fairness rotation state for the invitee set When generating nudge windows for a recurring series Then each returned window duration is ≤ L And no returned window places a participant with a "no after-hours" policy outside their working hours And the fairness rotation pointer advances to the next eligible participant for potential after-hours burden when needed And no participant receives after-hours burden in two consecutive occurrences unless no policy-compliant alternative exists, in which case the rationale flags "fairness fallback used"
Deterministic Conflict Resolution and Tie-Breaking
Given two or more candidate windows with final scores equal within 0.001 When the system orders candidates Then ties are broken deterministically using this key order: (1) lower total after-hours minutes, (2) earlier organizer local start time, (3) lexicographically smaller window_id And repeated runs with identical inputs produce identical ordering and selection And when no fully policy-compliant window exists, the system returns exactly one fallback window that minimizes total after-hours minutes and includes fallback_reason = "no_policy_compliant_window"
Latency SLA and Response Payload
Given production API traffic When the client requests personalized nudge window generation Then p95 end-to-end latency is ≤ 800 ms and p99 is ≤ 1500 ms over a rolling 24-hour window And the response contains 3–5 candidates each with fields: start_iso, end_iso (RFC 3339 with timezone offset), score (0.000–1.000), rationale (≤ 280 chars), decision_log_id And if fewer than 3 feasible candidates exist, the response returns all available (≥1) and sets insufficient_candidates = true
Multi-Timezone and DST-Safe Recurring Alignment
Given participants in at least two distinct IANA timezones and a weekly recurring series that crosses a DST transition for any participant When generating windows for the next four occurrences Then all participants with a "no after-hours" policy have occurrences within their local working hours And participants without such a policy experience a local start-time deviation of ≤ 60 minutes from the series target time And all timestamps are computed using IANA timezone rules for the specific occurrence date And no occurrence overlaps any participant's hard blackout periods
Decision Logging and Auditability
Given any nudge window generation request When processing completes Then a decision log entry is written containing: request_id, timestamp_utc, input_fingerprint, evaluated_windows_count, selected_window_ids, scoring_breakdown, fairness_state_before, fairness_state_after, policy_checks, tie_breaker_path, algorithm_version And the API response includes decision_log_id referencing the log entry And 99.9% of requests over a 30-day period have a retrievable log within 120 seconds of response And logs are retained and queryable for at least 30 days
Fairness Guardrails & Rotation Compliance
"As a program manager, I want fairness safeguards so that preference learning doesn’t concentrate inconvenient times on the same people."
Description

Integrate fairness constraints so personalization does not concentrate undesirable times on specific participants. Enforce rotation caps for early/late allocations per user per period, monitor cumulative after-hours burden, and reconcile with team-level fairness targets across timezones. Provide policy configuration, exception handling for critical meetings, and automated rebalancing over upcoming recurrences. Surface fairness metrics and violations in dashboards and export APIs, with alerting when thresholds are breached.

Acceptance Criteria
Personalization Guardrail: Do Not Concentrate Undesirable Times
Given a policy with maxUndesirablePerUserPerPeriod=2 and period=rolling 4 weeks And a user with historical preference for early starts And the user already has 2 undesirable allocations in the current period When the scheduler generates nudge options for a new meeting or recurrence Then no option presented assigns an undesirable time to that user And the allocation engine does not auto-assign that user an undesirable time And a decision record is stored with reason code "cap_guardrail"
Per-User Early/Late Rotation Caps
Given policy maxEarlyPerUserPerPeriod=2 and maxLatePerUserPerPeriod=2 with period=calendar month And early is defined as start < userWorkingWindowStart and late as end > userWorkingWindowEnd And a user has reached maxEarly for the current period When generating a schedule for an 8-instance recurring series Then the plan contains at most 2 early instances for that user within the period And remaining undesirable slots rotate to other eligible participants without exceeding their caps And if constraints cannot be satisfied, the system flags "no-feasible-rotation" and requests admin action or exception
Team Fairness Target Compliance Across Timezones
Given team policy maxUndesirableDeviation=1 per 4-week period And a team of N participants across at least 3 timezones When scheduling 6 weekly recurrences Then the count of undesirable allocations per participant in the period differs from the team mean by no more than 1 And no region receives more than 2 consecutive undesirable allocations across the series And the rotation order is recorded and retrievable via API
After-Hours Burden Monitoring and Threshold Alerts
Given after-hours is defined as minutes outside each participant’s working window And alertThresholdAfterHoursMinutesPerUser=180 per 4-week period When a proposed schedule would cause a user to exceed the threshold Then the system blocks auto-scheduling and presents alternative times that keep the user under threshold And if the meeting is organizer-confirmed anyway, an alert is emitted via email and webhook within 5 minutes including user, seriesId, deltaMinutes, and policyBreached
Policy Configuration and Critical-Meeting Exceptions
Given an admin can configure caps, periods, definitions, and exception rules via UI and API And a meeting is tagged critical with allowCapOverride=1 per user per period and justificationRequired=true When scheduling would otherwise breach a cap Then the system allows the override once per period for each impacted user after justification is provided And logs the override with actor, timestamp, policyId, reason, and compensatingActionPlanId And schedules compensating rotation so the next eligible recurrence shifts burden away from the overridden user
Automated Rebalancing of Upcoming Recurrences
Given the system detects an imbalance versus team targets for a recurring series And at least 2 future instances remain and compliant alternative slots exist on participants’ calendars When rebalancing is triggered Then a rebalanced plan is generated within 15 minutes that shifts burdens to underburdened participants without violating caps or hard constraints And upon organizer approval, updated invitations are sent with one-click confirmation And no more than 1 change per participant per rolling 7 days is introduced by rebalancing unless the meeting is tagged critical
Fairness Metrics, Dashboards, and Export APIs
Given required metrics: after-hours minutes per user per period, early/late counts, undesirable deviation, rotation sequence, exception counts When a schedule is created, updated, rebalanced, or an exception occurs Then dashboards reflect updated metrics within 5 minutes And an export API returns JSON and CSV with filters teamId, period, seriesId, and userId And policy violation events include reason codes and are queryable within the same period
Preference Explainability & Overrides
"As a user, I want to see why a time was suggested and quickly adjust my preferences so that I feel in control."
Description

Present concise explanations alongside suggestions (e.g., “prefers Tue–Thu mornings”) derived from the active features driving the choice. Offer user-level controls to adjust preferences (increase/decrease influence), pin or exclude specific windows, reset history, and disable for a meeting or series. Ensure explanations are faithful to model decisions, localized, and accessible. Log shown explanations and overrides to close the feedback loop and improve transparency and trust.

Acceptance Criteria
Inline Explanation on Suggestion Card
Given a user is viewing nudge windows with ranked suggestions, When suggestions are rendered, Then each suggestion displays a single-line explanation (<=80 characters) describing the dominant preference signal influencing its ranking. Given suggestions are re-ranked due to a live control change, When the list refreshes, Then the explanations update to reflect the new active signals without requiring a page reload. Given a suggestion is not influenced by any learned preference signals (all contributions < 10%), When displayed, Then the explanation reads "Based on availability only" and no preference is referenced. Given an explanation is shown, When rendered, Then it uses the user's locale for day names and time formatting and includes no sensitive personal data.
Faithful Attribution to Active Signals
Given the model’s feature contribution vector for a shown suggestion, When generating the explanation, Then only features with contribution >= 20% of total positive influence are named, ordered by weight. Given multiple features exceed the threshold, When composing text, Then at most the top 2 features are included (e.g., "prefers Tue–Thu mornings; 45 min length"). Given no feature meets the threshold, When composing text, Then the explanation omits preference language and states availability-based reasoning. Given a test harness with known weights, When validated across a representative dataset, Then 100% of sampled explanations name the same top contributing features as the model outputs.
Preference Adjustments and Per-Meeting Disable
Given a user opens Preference controls, When they increase or decrease a preference influence by one step, Then subsequent suggestions generated within the same session reflect the new weight and explanations mention the adjusted preference on affected suggestions. Given a user toggles "Disable preferences for this meeting," When suggestions regenerate, Then all preference features are excluded from scoring for that meeting instance and explanations display "Preferences disabled for this meeting." Given a recurring series creation, When "Disable for this series" is selected, Then all occurrences created inherit the disable flag and show availability-based explanations. Given the disable toggle is turned off, When suggestions regenerate, Then the system resumes using learned preferences.
Pin and Exclude Windows Controls
Given a user pins a time window, When suggestions are generated, Then pinned slots within the recurrence window are ranked above non-pinned slots and labeled "Pinned by you" in the explanation. Given a user excludes a time window, When suggestions are generated, Then no suggestions fall within excluded windows; if unavoidable, the UI shows a conflict notice and requires explicit override. Given both a pinned slot and conflicting learned preferences exist, When ranking, Then the pin takes precedence and the explanation prioritizes "Pinned by you" over preference signals. Given a user changes their working timezone, When viewing pinned/excluded windows, Then stored windows are converted to the new timezone and explanations render times in the current locale.
Reset Preference History Action
Given a user clicks "Reset preference history" and confirms, When the reset completes, Then all learned preference weights and derived signals for that user are cleared, while pinned/excluded windows remain unchanged. Given a reset has been performed, When generating new suggestions, Then explanations read "No learned preferences yet" until sufficient new interactions are captured. Given a reset is triggered, When data stores are checked within 15 minutes, Then preference history records for that user are removed from active stores and caches. Given a reset is performed, When auditing logs, Then a reset event with user ID, timestamp, and scope is recorded.
Logging of Explanations and Overrides
Given a suggestion list is shown, When a user views it, Then for each shown suggestion the system logs explanation text, referenced feature IDs, feature weights, locale, and anonymized user/team identifiers. Given a user performs an override (adjust influence, pin, exclude, reset, disable), When the action occurs, Then an event is logged with action type, scope (meeting/series/user), pre/post values, timestamp, and success/failure. Given logging is enabled, When inspecting the pipeline, Then no raw calendar titles, invitee emails, or free-text notes are stored; only hashed or redacted identifiers are retained. Given a data flow test, When 1,000 events are emitted, Then at least 99% are ingested and queryable within 5 minutes.
Localization and Accessibility Compliance
Given the app locale is set to a supported language, When explanations render, Then all strings are localized with correct day and time formats and pluralization for that locale. Given the app is used with a screen reader, When focus is on a suggestion, Then the explanation is announced in full and the "Pinned" or "Preferences disabled" states are included in the accessible name or description. Given color contrast testing, When measured, Then explanation text and badges meet WCAG 2.1 AA contrast ratios, and no information is conveyed by color alone. Given keyboard-only navigation, When navigating the suggestions, Then the controls to adjust preferences, pin/exclude, disable, and reset are reachable in a logical tab order with visible focus indicators.
Performance & Experimentation Analytics
"As a product owner, I want measurable evidence that Preference Memory improves scheduling and equity so that we can iterate and justify rollout."
Description

Instrument key outcome metrics (acceptance rate, declines, time-to-schedule, after-hours exposure, fairness distribution) to quantify impact of Preference Memory. Provide experimentation support (A/B, phased rollouts) with guardrail monitors, cohorting, and statistical reporting. Expose org- and team-level dashboards and exports, and trigger alerts on regressions or fairness breaches. Feed aggregated outcomes back into model tuning and policy defaults.

Acceptance Criteria
Instrumentation Completeness & Accuracy
- System computes core metrics: acceptance_rate, decline_rate, time_to_schedule (p50, p90), after_hours_exposure (% meetings outside participant work windows), and fairness_distribution (stddev of after-hours exposure across participants) per org/team/day. - Metric definitions match the approved analytics spec v1.0; accuracy tolerance: <= 0.5 percentage points for rate metrics and <= 1 minute for time metrics when compared to an offline reference job over a 24h validation sample. - Data freshness: >= 99% of metric updates available within 15 minutes of event; late or missing data <= 0.1% of events per 24h. - Event schema includes meeting_id, org_id, team_id, experiment_arm, source_timezone, utc_timestamp, duration, decision; ingestion is idempotent (no duplicate metrics on replay). - PII excluded from metric stores; participant identifiers are salted hashes; privacy checks pass on CI.
Experiment Assignment Integrity & Stickiness
- Eligible participants are bucketed by stable unit_id = hash(org_id:participant_id) with configurable allocation (default 50/50) and stratification by org size; assignment is sticky for 90 days or until experiment end. - Exposure is logged at first nudge render with experiment_id, arm, version, timestamp, and eligibility flags; exposure log drop rate <= 0.5%. - Treatment contamination rate < 0.5% (participants receive only their assigned arm); verified via assignment vs. exposure reconciliation. - Phased rollout supported via percentage ramps (e.g., 1% -> 10% -> 50% -> 100%); configuration changes propagate globally within 5 minutes p95. - Exclusion rules applied: orgs with < 10 active participants or < 200 weekly meetings default to control until minimum sample size is reached.
Guardrails Monitoring and Auto-Rollback
- Guardrails continuously compute control vs. treatment deltas for after_hours_exposure and fairness_distribution; breach defined as: after_hours_exposure +1.0 pp absolute or fairness stddev +5% relative sustained for 2 consecutive days with p < 0.05. - On breach, system auto-pauses ramps and rolls back to previous safe allocation within 2 minutes p95; change is recorded in audit log. - Alerts are sent to on-call channel and email with experiment_id, metric(s) breached, effect size, confidence, and deep link to dashboard within 60 seconds of detection p95. - Manual global kill switch disables Preference Memory nudges within 2 minutes p95 and confirms status in admin UI.
Cohorting, Segmentation, and Privacy Controls
- Dashboards support filters by org, team, timezone cluster, role, meeting type, meeting length bucket, and experiment arm; queries return within 3 seconds p95 for cohorts with >= 200 meetings in window. - Small cohorts are protected: results suppressed or aggregated when cohort < 50 participants OR < 200 meetings; privacy budget logged. - Each view displays denominator and time window explicitly; metrics reconcile to totals within 0.5 percentage points. - RBAC enforces: org admins see only their org; internal analysts see cross-org aggregates only; access attempts are audited and rate-limited.
Statistical Reporting and Power Calculations
- Experiment results display effect size, 95% confidence intervals, p-values, and Bayesian posterior summaries for primary metrics. - Sequential testing control applied (e.g., alpha spending) maintaining overall alpha <= 0.05; methodology and current alpha spending shown. - Pre-launch console computes minimum sample size and MDE given power >= 80%; launch blocked if criteria unmet. - CUPED/covariate adjustment applied when a 14-day baseline exists; diagnostics (variance reduction %) reported.
Dashboards, API, and Exports Performance
- Org and team dashboards load within 2.5 seconds p95 for last 30 days; data tiles show time-to-schedule, acceptance, declines, after-hours exposure, fairness distribution. - Users can export CSV and JSON of aggregated metrics by day and by team; files include schema_version and UTC timestamps; export completes within 60 seconds for 90 days of data. - Public API endpoints /v1/metrics and /v1/experiments support filtering, pagination, and rate limit of 60 req/min/token; 99th percentile latency < 1s for cached responses. - Access requires OAuth2 or API key; all exports and API calls are audited with user_id, org_id, filters, and timestamp.
Aggregated Outcomes Feedback to Model & Policies
- Daily aggregated outcomes (no PII) are written to feature store with schema: org_id, team_id, timezone_cluster, acceptance_rate, decline_rate, time_to_schedule_p50, after_hours_exposure, fairness_stddev, meetings_count, schema_version. - Model training jobs consume only tables passing k-anonymity (k >= 50) and freshness checks (< 24h); jobs fail closed if checks fail. - Policy defaults (e.g., nudge window widths) auto-adjust only when 14-day moving average shows statistically significant improvement (p < 0.05) in primary guardrail metrics; changes are versioned with rollback capability. - Offline replay evaluation predicts observed experiment gains within ±20% relative error before enabling new model/policy in >10% traffic.

Guest Link

Bring externals into the same one‑tap flow. Partners and vendors get a secure, magic‑link mini page mirroring the two ranked choices, no login required. Their selections roll into the same tally and quorum rules, and confirmations include calendar files and conferencing links. Cross‑company scheduling stays fast without Slack/Teams sprawl.

Requirements

Secure Magic Link Generation & Expiry
"As an organizer scheduling with external partners, I want to generate secure, expiring guest links that don’t require login so that guests can pick times quickly without risking unauthorized access."
Description

Generate cryptographically secure, single-purpose magic links that allow external guests to participate without login while protecting meeting details. Each link must be tied to a specific scheduling instance, support configurable expiration (time-based or upon confirmation), and offer organizer controls for single-use or limited multi-use access. Include revocation, domain allow/block lists, rate limiting, and bot protection to prevent abuse. Tokens should be opaque, non-guessable, and stored hashed. Links must not expose conferencing details until a time is confirmed. All events (creation, open, selection, revoke) are logged for audit and compliance, and the mechanism integrates with existing permissions and event objects in TimeTether.

Acceptance Criteria
Secure Opaque Token Generation and Hashed Storage
Given an organizer creates a guest link for a scheduling instance When the system generates the magic-link token Then the token contains at least 128 bits of cryptographically secure entropy, is URL-safe, and encodes no PII or internal IDs And the token value is stored only as a keyed HMAC-SHA-256 (or stronger) hash using an application secret; the raw token is never persisted or logged And token comparisons are performed in constant time And responses for invalid and expired tokens use the same generic message and HTTP status to prevent token enumeration
Link Scoping and Permissions Integration
Given a magic link is generated for scheduling instance A When the link is used against any endpoint for instance B Then the request is denied with no instance B data leaked and a generic unavailable response is returned And access via a guest link exposes only the guest mini page and does not reveal organizer-only controls And current event permissions are enforced on every request; if guest access is disabled for the event, the link returns unavailable
Access Lifecycle Controls: Expiration and Use Limits
Given the organizer sets a relative TTL or absolute expiry timestamp on a magic link When the current time exceeds the configured expiry Then the link becomes unusable within 60 seconds and returns HTTP 410 Gone with an expiry message Given the organizer enables expire-upon-confirmation When any guest completes a valid time selection via the link Then the link is immediately invalidated and further opens return unavailable Given the organizer selects single-use When the first valid submission is recorded Then subsequent opens or submissions for that token are blocked Given the organizer sets a limited multi-use count N When N valid submissions have completed Then the token is invalidated on the next request And changes to TTL or use count take effect on the next request and are audit-logged
Revocation with Immediate Invalidation and Consistent UX
Given an organizer clicks Revoke on a magic link or calls the revoke API When the action is confirmed Then the token is invalidated globally within 5 seconds and any further requests return a revoked message with no meeting details And any in-progress session attempting to submit after revocation is rejected and the UI updates to the revoked state And revoking one token for an event does not affect other active tokens for the same event
Abuse Protection: Rate Limiting, CAPTCHA, and Domain Allow/Block
Given repeated opens from the same IP or token source When more than X opens occur within Y minutes (X and Y are configurable) Then subsequent requests return 429 Too Many Requests with a Retry-After header and an audit event is recorded Given more than T failed token validations from a source (configurable) When the next open occurs Then a bot challenge (e.g., CAPTCHA) is required before the page is rendered; failing the challenge blocks access Given a guest submits an email to receive confirmation artifacts When the email domain is on the block list Then the submission is rejected with HTTP 403 and a clear reason And when an allow list is configured, only emails from allowed domains are accepted And all form submissions require a valid CSRF token bound to the magic link session; missing or invalid CSRF returns 403
Conferencing Details Redaction Until Confirmation
Given a guest opens the magic link prior to making a selection When inspecting the page source, network calls, and link preview metadata Then no conferencing URLs, meeting IDs, or dial-ins are exposed Given a guest has not confirmed a time When attempting to fetch conferencing details via any API using only the magic link Then the request is denied (403/404) and no details are returned Given a guest successfully confirms a time via the magic link When the confirmation screen is displayed and the confirmation email is sent Then both include an .ics attachment and the conferencing link appropriate to the event And OpenGraph/Twitter meta tags never include conferencing details at any time
Comprehensive Audit Logging for Magic Links
Given magic link lifecycle events occur (creation, open, selection, revoke, expiry, rate-limit, domain-block) When each event happens Then an audit record is written with timestamp (UTC), scheduling instance ID, token fingerprint (hash), actor type (guest/internal/system), source IP (masked per policy), user-agent hash, event type, and outcome And audit records are append-only; there is no API to edit or delete individual records outside of retention policies Given an admin with appropriate permissions When querying audit logs for a scheduling instance Then they can filter by event type and date range and export results with guest PII redacted per policy Given the retention policy reaches its limit When the retention job runs Then expired audit records are purged or archived and a retention action event is recorded
Guest Mini Page with Timezone Auto-Detect
"As an external guest, I want a simple page showing the two proposed times in my local timezone so that I can choose with one tap without creating an account."
Description

Provide a lightweight, responsive mini page accessed via magic link that mirrors the two ranked time choices from the internal scheduler. Automatically detect the guest’s timezone (with manual override) and render local times, duration, meeting title, and organizer identity. Offer one-tap selection with clear visual ranking, a deadline countdown if set, and accessible UI (WCAG 2.1 AA). Handle edge cases such as expired or revoked links, closed polls, or reached quorum with informative states and a path to request a new link. The page must load fast on mobile, support localization, and track basic engagement analytics for reliability monitoring.

Acceptance Criteria
Timezone Auto-Detect with Manual Override
Given a guest opens the magic link on a supported browser When the mini page loads Then the system auto-detects the guest’s timezone using Intl API with IP-based fallback and sets it as the active timezone And all displayed times reflect the active timezone and the guest’s 12/24-hour preference inferred from locale And detection accuracy is at least 95% versus ground-truth on a curated test set And no location permission prompt is required And if auto-detect fails, the default timezone is UTC with an inline alert Given the guest opens the timezone control When they search or scroll the timezone list Then they can select any IANA timezone and the UI updates immediately And the override persists across refreshes for 24 hours or until the link expires And an analytics event timezone_override is emitted with anonymized tz_id
Mirror Ranked Choices with Localized Display
Given a valid meeting token with two ranked options exists When the guest opens the mini page Then exactly two time options are rendered with rank badges 1 and 2 in the same order as the internal scheduler And each option shows local date, start time, and duration (or end time) with DST handled correctly And the meeting title and organizer name/avatar are visible above the options And the UI reflects the guest’s active timezone and locale-specific date/time formats And an impression analytics event page_view is emitted with anonymized locale and tz_id
One-Tap Selection Applies Tally and Quorum Rules
Given the guest has not yet responded and the poll is open When the guest taps a time option Then their selection is recorded exactly once and acknowledged within 1 second with a selected state And the internal tally is updated and visible to the organizer within 5 seconds And if the selection satisfies quorum, the meeting is finalized and the guest sees a confirmation state And analytics events option_selected and tally_update_sent are emitted without PII And repeated taps or reloads do not create duplicate votes
Deadline Countdown Visibility and Behavior
Given the organizer has set a response deadline When the mini page loads Then a countdown timer is displayed showing time remaining in the guest’s active timezone And the timer updates at least once per minute without excessive battery drain And when the deadline passes, selection controls are disabled and a poll closed message is shown And if no deadline is set, no countdown element is rendered
Accessible UI Meets WCAG 2.1 AA
Given a guest using keyboard-only navigation When interacting with all controls on the mini page Then logical tab order, visible focus indicators, and skip-to-content are present And all interactive elements meet a minimum hit area of 44x44px on mobile And color contrast ratios are >= 4.5:1 for text and >= 3:1 for large text/icons And ARIA labels, roles, and names are provided for screen reader compatibility; live regions announce status changes And the page remains functional and readable at 200% zoom and with reduced motion enabled And all criteria pass automated axe-core checks and manual assistive tech tests with NVDA and VoiceOver
Edge States and Recovery Paths
Rule: If the magic link token is expired, Then show an Expired Link state with a CTA to request a new link; the CTA triggers a notification to the organizer and confirms submission to the guest Rule: If the magic link token is revoked, Then show a Revoked Link state with organizer contact hint and no meeting details revealed Rule: If the poll is closed (deadline passed) and quorum not met, Then show a Poll Closed state and CTA to request a new link Rule: If quorum is already reached, Then show a Finalized state with the selected time rendered in the guest’s timezone and provide add-to-calendar (.ics) and conferencing join link Rule: Tokens are signed and time-bound with at least 128 bits of entropy; rate limiting of 10 invalid attempts per minute per IP is enforced; no PII is exposed in the URL
Mobile Performance and Localization
Given a mid-tier mobile device on a Slow 4G profile When loading the mini page Then Largest Contentful Paint <= 2.5s, Time to Interactive <= 3.0s, Total Blocking Time <= 300ms, and First Input Delay <= 100ms in controlled lab tests And transfer size <= 250KB (gzipped) and <= 20 requests on first load And the page is localized for at least English, Spanish, and German with automatic locale detection and a manual language switcher; all static and dynamic text is translated And date/time formats and numbering follow the selected locale; RTL layouts render correctly for RTL languages And a reliability heartbeat and page_load analytics event are emitted with sampling >= 95%
Unified Tally Integration & De-duplication
"As an organizer, I want guest selections to roll into the same tally and quorum rules as my team so that the meeting confirms automatically when criteria are met."
Description

Record guest selections in real time and feed them into TimeTether’s existing tally, quorum, and fairness rotation logic without special handling. Ensure idempotent submissions, preventing duplicate or conflicting votes via token-level locking and optional email capture for de-duplication. Support organizer-defined caps on the number of external responses counted and reflect tallies in organizer views instantly. Emit internal events/webhooks on guest selection updates for downstream automation. Respect existing work windows and weighting rules so external votes neither overrule nor bypass configured constraints.

Acceptance Criteria
Real-time Guest Selection Feeds Unified Tally
Given an active poll with two ranked choices and a defined quorum threshold And a valid guest magic-link token that has not yet submitted When the guest selects one of the available choices and submits Then the unified tally increases the external response count for that choice by 1 And the overall vote count updates accordingly And quorum status and fairness rotation recompute using the same logic applied to internal votes And the organizer’s tally API and UI reflect the new counts as part of the single unified tally
Idempotent Token Submission and Update
Given a valid guest token T linked to poll P When a submission for choice A is received using token T And a second submission for choice B is received using the same token T at any later time Then exactly one external respondent is counted for token T in poll P And the counted choice is updated to B without increasing the total counted external responses And two concurrent submissions using token T result in one persisted selection and no double count And the API audit for the second submission indicates action=updated and idempotent=true
Email-Based De-duplication Across Tokens (Opt-in)
Given the organizer has enabled email capture with de-duplication mode "single counted per email" And token T1 submits a selection for email e@example.com choosing option A When token T2 submits a selection for the same email e@example.com choosing option B for the same poll Then the system treats e@example.com as one respondent and updates the counted selection to option B And the total counted external responses increases by 1 on the first submission only and does not increase on the second And the tally shows exactly one counted external response for e@example.com, associated with option B And the API audit for the second submission has deduped=true and linked_token=T1 And when email capture is disabled, submissions from T1 and T2 are counted independently regardless of matching emails
External Response Cap Enforcement and Overflow Handling
Given the organizer sets an external response cap N for a poll And the tally currently has N counted external responses When an additional guest submits a valid selection Then the submission is accepted but marked counted=false due to cap_reached And quorum and fairness rotation calculations remain unchanged by this submission And the organizer view displays overflow_external_responses incremented by 1 and cap status reached And guest confirmation UI communicates that their response may not be counted due to the cap And when N=0 all external submissions are treated as overflow and not counted
Instant Organizer View Reflects Guest Selections
Given the organizer has the tally view open for a poll When a guest submits or updates a selection via the magic link Then the organizer’s UI reflects the updated tally and any recalculated quorum/fairness indicators within 2 seconds of submission time And the GET /polls/{id}/tally endpoint returns the updated counts within 2 seconds of submission time And no manual refresh is required for the UI when real-time channels are available; otherwise polling at 2s intervals surfaces the update
Event/Webhook Emission on Guest Selection Create/Update
Given a webhook subscription exists for event type guest.selection.updated When a guest creates a new selection or updates an existing one Then a webhook is delivered within 3 seconds with fields: event_type, poll_id, token_id, choice_id, counted (true|false), action (created|updated), previous_choice_id (if any), email (if provided), idempotency_key, timestamp And deliveries are at-least-once with a stable idempotency_key to support receiver de-duplication And deliveries include a valid X-TT-Signature header verifiable with the shared secret And failed deliveries are retried with exponential backoff for at least 24 hours And submissions not counted due to cap emit counted=false with reason=cap_reached
Respect Work Windows and Weighting in Unified Logic
Given a poll configured with work windows and fairness weighting rules When external guests submit selections that favor times outside configured work windows or skew fairness rotation Then the guest-facing options only include times that comply with work windows And the tally, quorum evaluation, and fairness rotation never select a time outside configured work windows And external votes are weighted identically to internal votes within the same rule set and cannot override weighting constraints And any attempt to submit a non-present or disallowed choice is rejected with HTTP 400 and is not counted
Calendar Files and Conferencing Delivery
"As an external guest, I want a confirmation with a calendar file and conferencing link so that the meeting is added to my calendar without extra steps."
Description

Upon quorum confirmation, automatically send external guests a confirmation email containing an .ics file and one-click Add-to-Calendar links (Google, Outlook, Apple). Include the host’s conferencing link (Zoom, Meet, Teams, or default bridge) retrieved from the confirmed time’s configuration. Ensure links are only revealed post-confirmation and are updated upon reschedule or cancellation with follow-up notifications and updated calendar data. Populate calendar metadata (title, description, organizer, attendees, timezone, reminders) and support UTF-8 and locale-specific formatting. Provide a fallback downloadable .ics from the mini page if email delivery fails.

Acceptance Criteria
Post-Quorum Confirmation Email to External Guests
Given an external guest used a Guest Link mini page and quorum is reached for a time, When the meeting auto-confirms, Then the guest receives a confirmation email that includes an attached .ics file and visible one-click Add-to-Calendar links for Google, Outlook, and Apple. And the email and .ics both include the host’s conferencing link for the confirmed time. And the mini page and any prior emails do not reveal the conferencing link or .ics prior to confirmation. And the confirmation email shows the host as organizer and the guest as an attendee.
Conferencing Link Sourcing and Insertion
Given the confirmed time references a conferencing provider in the host configuration, When the confirmation content is generated, Then the conferencing URL inserted into the email and .ics is taken from that provider (Zoom, Meet, Teams, or default bridge) for the confirmed slot. And if the configured provider lacks a resolvable meeting URL, then the default bridge is used. And exactly one conferencing link is present across the email body, Add-to-Calendar links, and .ics Location/Description, and all occurrences match the same URL.
Add-to-Calendar Links Generate Correct Event
Given a recipient clicks “Add to Google”, When Google Calendar opens, Then the event prefill contains the correct title, start/end time with correct timezone offset, description including the conferencing link, and the host as organizer. Given a recipient clicks “Add to Outlook”, When Outlook Calendar opens (web or desktop as applicable), Then the event prefill contains the correct title, start/end time with correct timezone offset, description including the conferencing link, and the host as organizer. Given a recipient clicks “Add to Apple”, When the .ics is opened in Apple Calendar, Then the created event matches the confirmed details with correct timezone and conferencing link.
Calendar Metadata Completeness and Localization
Given the meeting title and description include non-ASCII characters and multiple locales (e.g., “Sprint révision — Tokio開発”), When the .ics is generated, Then it is UTF-8 encoded and contains SUMMARY, DESCRIPTION, ORGANIZER (host), ATTENDEE (each guest), UID, DTSTART/DTEND with TZID, and at least one VALARM per configured reminder. And importing the .ics into Google, Outlook, and Apple preserves all characters and fields without warnings or data loss. And the confirmation email subject and body format dates/times and numbers according to the guest’s locale (e.g., en-US vs en-GB) while the .ics remains standards-compliant.
Reschedule Triggers Updated Invites and Links
Given a confirmed meeting is rescheduled to a new time, When the reschedule is confirmed, Then external guests receive an update email containing an updated .ics that reuses the same UID and increments SEQUENCE, with updated DTSTART/DTEND and timezone. And the Add-to-Calendar links reflect the new time and details. And if the conferencing provider or link has changed, the new link replaces the old one in the email, mini page, and .ics. And importing the updated .ics causes major calendar clients to replace the prior event instance with the new time.
Cancellation Notification and Calendar Update
Given a confirmed meeting is canceled, When cancellation is issued, Then external guests receive a cancellation email containing an .ics with METHOD:CANCEL that removes the event when processed by Google, Outlook, and Apple. And the Guest Link mini page reflects a canceled state and does not display any conferencing link or Add-to-Calendar options. And any previously accessible Add-to-Calendar links are disabled or return a message indicating the event is canceled.
Email Delivery Failure Fallback via Mini Page
Given confirmation occurred but email delivery to an external guest fails per the system’s retry/bounce policy, When the guest accesses the magic-link mini page, Then a secure “Download .ics” control is available that provides the current event .ics containing all required metadata and the conferencing link. And the fallback is only available post-confirmation, requires the original magic link token, and is rate-limited. And delivery failure is logged with a correlation ID visible in support telemetry.
Guest Communications & Reminders
"As an organizer, I want TimeTether to notify guests and send reminders automatically so that I don’t have to manually coordinate across companies."
Description

Send lifecycle emails to external guests: invitation (with magic link), selection receipt, confirmation, reschedule, cancellation, and pre-meeting reminders (organizer-configurable timing). Ensure messages are branded, localized, and include unsubscribe and privacy disclosures. Track delivery, open, and bounce events; retry on transient failures. Provide organizer visibility into guest notification status and a manual resend option. No Slack/Teams account is required for guests; all communication occurs via email and the mini page.

Acceptance Criteria
Guest Invitation with Magic Link
Given an organizer includes at least one external guest on a meeting and clicks Send Invites When the system generates the guest invitation Then an invitation email is sent to each external guest within 60 seconds containing a unique, single-use magic-link URL to the guest mini page And the magic link opens the mini page without requiring login from modern desktop and mobile browsers And the magic link encodes the meeting context and two ranked time choices And the magic link expires on meeting cancellation or 30 days after issuance, whichever comes first And no Slack or Teams account is required for the guest to access or respond
Localized Branded Emails with Unsubscribe and Privacy
Given the workspace has branding assets and a default locale configured, and the guest has a preferred locale inferred or provided When any lifecycle email (invitation, receipt, confirmation, reschedule, cancellation, reminder) is generated for an external guest Then the email uses the workspace logo and color theme in header/footer And all static strings are localized to the guest’s preferred locale with fallback to the organizer’s default, then English And the email includes a functional unsubscribe link that manages non-transactional reminders And the email includes a visible link to the privacy policy And images include alt text and the email renders legibly in light and dark modes
Selection Receipt Email
Given an external guest submits their time selection via the magic-link mini page When the submission is accepted by the system Then a selection receipt email is sent to that guest within 60 seconds summarizing their selected option(s) and submission timestamp And the email contains a link to update their selection until a final meeting time is confirmed or the selection window closes And duplicate submissions within 30 seconds result in a single receipt email And guests unsubscribed from reminders still receive the transactional selection receipt
Confirmation, Reschedule, and Cancellation Emails with Calendar and Conferencing
Given quorum rules select a final meeting time including the external guest When the meeting is confirmed Then the guest receives a confirmation email within 60 seconds that includes the confirmed date/time in the guest’s timezone, the conferencing join link, and an attached .ics file with correct DTSTART/DTEND, UID, ORGANIZER, TZID, and conferencing details When the organizer reschedules the meeting Then the guest receives a reschedule email within 60 seconds with an updated .ics (METHOD:REQUEST, incremented SEQUENCE) and updated conferencing/time details When the organizer cancels the meeting Then the guest receives a cancellation email within 60 seconds with an .ics cancellation (METHOD:CANCEL) and clear cancellation messaging And all times in these emails display with an explicit timezone label matching the guest’s locale
Pre-Meeting Reminders Timing and Unsubscribe
Given the organizer configures reminder timings (e.g., 24 hours and 1 hour before start) When the meeting is confirmed Then reminder emails are scheduled at the configured offsets in the guest’s local timezone And exactly one reminder is sent per configured offset (duplicates suppressed) And reminder emails include the conferencing link and any organizer-allowed reschedule/cancel link And guests who have unsubscribed via the email link do not receive reminders And if the meeting start time changes, previously scheduled reminders are canceled and new reminders are scheduled accordingly
Delivery Tracking, Retry, and Bounce Handling
Given any lifecycle email to an external guest is dispatched via the email provider When delivery webhooks/events are received Then the system records per-email events for Queued, Sent, Delivered, Opened, Clicked, Soft Bounced, Hard Bounced with timestamps And transient failures (e.g., soft bounces, timeouts) are retried with exponential backoff up to 5 attempts within 24 hours And permanent failures (hard bounces, invalid address) stop retries, mark the address as hard-bounced, and suppress further sends to that address And the original send request is updated with final status within 5 minutes of last provider event
Organizer Visibility and Manual Resend
Given an organizer opens the guest notifications panel for a meeting When the panel loads Then the organizer sees per-guest notification rows showing last message type, current status (Queued, Sent, Delivered, Opened, Clicked, Soft Bounced, Hard Bounced, Unsubscribed), and last-event timestamp And the organizer can manually resend invitation, confirmation, reschedule, cancellation, or reminder emails per guest And manual resend is disabled for hard-bounced addresses until the email is corrected And manual resend respects unsubscribe preferences (transactional emails allowed; reminders blocked) And each resend action is logged with actor, timestamp, message type, and provider message ID
Admin Controls & Audit Logging for Guest Links
"As a workspace admin, I want centralized control and auditability of guest links so that we maintain security and compliance when scheduling with externals."
Description

Offer admin and organizer controls to view, revoke, regenerate, and configure guest links per meeting: expiry policy, usage limits, domain allow/block lists, and maximum counted responses. Provide an activity log with timestamps for link creation, opens, selections, confirmations, and revocations, with export options for compliance. Support org-level defaults and policies, data retention settings, and GDPR-compliant deletion of guest data. Expose management and reporting endpoints via API with scoped permissions for automation.

Acceptance Criteria
Revoke and Regenerate Guest Link Per Meeting
Given a meeting with an active guest link When an Admin or the meeting Organizer clicks Revoke in the meeting’s Guest Link controls Then the revoked link becomes unusable within 10 seconds and returns HTTP 410 Gone And the guest mini page displays “This link has been revoked” And no new opens, selections, or confirmations are accepted via the revoked link And an audit event "link_revoked" is recorded with actor_id, meeting_id, link_id, and UTC timestamp When a new link is generated by the Admin or Organizer Then a unique link URL is created and visible in the UI and via API And an audit event "link_generated" is recorded referencing the new link_id and superseded link_id And previously collected responses remain intact and are not duplicated
Configure Expiry Policy, Usage Limit, and Max Counted Responses
Given an Admin sets an absolute expiration for the guest link (date, time, timezone) When current time is greater than or equal to the expiration Then the link returns HTTP 410 and displays “This link has expired” And an audit event "link_expired" is recorded with UTC timestamp Given an Admin sets a usage limit of X unique guest submissions When the Xth unique submission is received Then further submissions are blocked with HTTP 429 and a UI message “Submission limit reached” And an audit event "usage_limit_reached" is recorded with count = X Given an Admin sets a maximum counted responses value of K When more than K unique submissions are received Then submissions after the Kth are accepted but marked "not_counted" and excluded from tally/quorum calculations And the organizer UI and API report counted = K and not_counted = total_submissions - K
Domain Allow/Block List Enforcement
Given an Admin configures an allow list and/or block list of email domains for a meeting’s guest link When a guest enters an email on the mini page Then the domain check is case-insensitive and trims whitespace And if the domain is on the block list, submission is prevented with message “Email domain not allowed” And if an allow list exists and the domain is not on it, submission is prevented with the same message And if the domain passes policy, the guest can proceed to submit selections And an audit event is recorded for each failed validation attempt with fields: event_type = "domain_validation_failed", domain, reason, UTC timestamp (no full email stored)
Activity Audit Log Capture, Filtering, and Export
Given a meeting uses a guest link When any of the following occurs: link_created, link_opened, selection_submitted, confirmation_sent, link_revoked, link_expired, settings_changed, export_started, export_completed Then an immutable audit entry is recorded with fields: event_type, UTC timestamp (ISO 8601), meeting_id, actor (user_id or "guest"), link_id, and minimal metadata (email_domain only for guests) And audit entries are viewable in the UI with pagination and filters for date range, event_type, actor, and meeting_id And the audit log can be exported in CSV and JSON formats, honoring applied filters, for up to 100,000 rows per export And exports include a checksum and are available for secure download for 7 days And audit data retention follows the configured policy; expired entries are deleted or anonymized and no longer appear in UI or exports
Org-Level Defaults and Policy Enforcement
Given an Org Admin sets org-level defaults for guest link settings (expiry policy, usage limits, max counted responses, domain allow/block lists, data retention) When a new meeting is created Then the meeting inherits these defaults and they are visible in the meeting’s Guest Link settings UI and via API And organizers may override a setting only if it is not policy-locked by the Org Admin When a setting is policy-locked Then the corresponding meeting-level control is read-only and labeled with a policy indicator in UI and API And all changes to org-level defaults and meeting-level overrides are recorded as audit events with actor and UTC timestamp
GDPR-Compliant Guest Data Deletion and Retention
Given an organization retention period of R days is configured When R days have elapsed for guest-related records Then guest personal data (e.g., email, name, IP) tied to the guest link is deleted or irreversibly anonymized And audit entries retain only non-PII fields (event_type and timestamps) per policy Given a verified GDPR erasure request for a specific guest identity When an Admin executes deletion for that identity and confirms Then all associated guest data is deleted or anonymized across primary storage within 30 days and from backups on their next scheduled purge And subsequent exports and API responses exclude the deleted PII And an audit event "guest_data_deleted" is recorded without storing the raw personal data
API Management and Reporting with Scoped Permissions
Given a service token with scope "guest_link.write" When calling API endpoints to create, revoke, regenerate, and configure guest links for authorized meetings Then the API returns 2xx responses and persisted state reflects the requested changes And audit events are recorded for each operation Given a token lacking required scopes (e.g., only "guest_link.read") When calling write endpoints Then the API returns 403 with error "insufficient_scope" Given a token with scope "guest_link.read" and/or "audit.read" When calling endpoints to view link settings and fetch audit logs/exports Then the API returns 2xx and data limited to the token’s org and meeting permissions And all API responses include standard error codes and request_ids for traceability

Equity Ledger

A tamper‑evident, region‑level log of after‑hours rates, fairness scores, and changes over time. Drill into teams and series, filter by cadence or role, and export clean CSV/Parquet in one click. Equity owners get transparent, defensible evidence for audits and a single source of truth for improvement tracking.

Requirements

Immutable Hash-Chained Equity Ledger
"As an equity program owner, I want a tamper-evident ledger of fairness metrics and changes so that I can demonstrate integrity and defend audits with verifiable evidence."
Description

Implement an append-only, tamper-evident ledger that records region-level after-hours rates, fairness scores, and configuration changes with cryptographic hash chaining and time-stamped entries. Each record captures actor identity, source dataset, computation version, and policy context, enabling end-to-end provenance. Ledger immutability is enforced at the storage layer with write-once semantics and verified via periodic integrity checks; any mismatch triggers alerts and blocks writes until reconciled. The ledger ingests metric snapshots from the scheduling engine after each computation run and stores pre/post-change deltas when policies or work windows are updated. Data is replicated cross-region for durability, supports retention policies, and exposes read-optimized indexes for fast queries by region, date, team, and series. The outcome is a defensible, audit-ready source of truth for fairness tracking.

Acceptance Criteria
Ingest Run Appends Hash-Chained Entries with Complete Provenance
Given a scheduling engine computation run completes and emits region-level metrics (after_hours_rate, fairness_score) with run_id And a previous ledger head (prev_hash) exists per region (or null for first entry) When the ledger ingest service processes the run Then it appends exactly one entry per region linking to the region's prev_hash via cryptographic hash chaining And each entry includes non-null provenance: actor_id, actor_type, source_dataset_id_or_checksum, computation_version, policy_context_id, policy_version And each entry has an RFC 3339 UTC timestamp that is >= the previous entry's timestamp for that region And the service returns entry_id and entry_hash for each appended entry And no updates or deletes are performed on existing entries (0 mutations in audit log) And immediate post-write verification confirms chain continuity per region (new.prev_hash == prior.last.hash)
Periodic Integrity Check Blocks Tampered Chains
Given the ledger contains entries across one or more regions When the integrity job runs every 15 minutes Then it recomputes and verifies hash chains from the last stored anchor per region And on any mismatch it sets region status to "corrupt", emits a P1 alert to on-call within 2 minutes with region_id and offending entry_id And it blocks new writes to affected regions, returning HTTP 503 with error_code=LEDGER_INTEGRITY_BLOCK And it resumes writes automatically after a subsequent integrity run passes for the region
Write-Once Storage Enforcement
Given an API client attempts to modify or delete an existing ledger entry When using PUT, PATCH, DELETE, or any mutation call Then the request is rejected with 405 Method Not Allowed (or 409 Conflict) and no data changes occur And the attempt is recorded in the audit log with actor_id, entry_id, operation, and reason "write-once" And direct storage-level mutation attempts are prevented by WORM policy and logged by the storage provider
Cross-Region Replication and Read Availability
Given primary-region writes occur at up to 200 writes/sec sustained When replication is enabled to at least two secondary regions Then RPO <= 60 seconds and RTO <= 5 minutes for a primary-region outage And read requests in any healthy region succeed with p95 latency <= 250 ms for point lookups and <= 600 ms for date-range scans returning <= 10k rows during failover And no acknowledged write is lost beyond the stated RPO after recovery
Read-Optimized Index Performance by Region/Date/Team/Series
Given a corpus of 50 million ledger entries and indexes on (region_id,date), (team_id,date), (series_id,date) When executing queries filtered by region and date range, or by team/series and date range Then p95 latency <= 400 ms and p99 <= 1 s for result sets <= 10k rows And the query plan uses the appropriate index in > 99% of requests sampled And returned counts and sample rows match ground-truth validations
Retention Policy Execution Preserves Chain Verifiability
Given a retention policy of 24 months is configured with monthly pruning When the retention job runs Then entries older than 24 months are archived to cold storage and removed from hot indexes And a cryptographic anchor (segment hash or Merkle root) for each pruned segment is persisted And subsequent integrity checks verify chain continuity using anchors across pruned segments And queries spanning pruned periods return 410 Gone with archive location metadata And entries on legal hold are exempt from pruning and reported
Policy/Work Window Change Delta Recording
Given a change to fairness policy or work window is approved and applied When the change is executed Then the ledger writes a delta record containing references to prior_config_id and new_config_id, a machine-readable diff of changed fields, actor_id, and effective_at (UTC) And the delta record is linked into the hash chain for each affected region/team/series And the next computation ingest records both pre-change and post-change metric snapshots adjacent to the delta And querying deltas by policy_context_id and date range returns complete, correctly ordered results
Region-Level Metrics Aggregation and Timezone Normalization
"As a data analyst, I want accurate region-level calculations normalized across timezones so that cross-region comparisons are fair and actionable."
Description

Deliver a computation pipeline that aggregates after-hours rates and fairness scores at the region level with accurate timezone normalization. Users are mapped to regions via profile location and org metadata, with daylight saving transitions and local holidays accounted for. Metrics support multiple rollups (7/30/90 days) and segmentation by cadence (weekly/biweekly/monthly), attendee role (host/participant), and meeting type. The pipeline is idempotent and backfills historical data from calendar ingestion, tagging each run with schema and algorithm versions for reproducibility. Normalized metrics are stored with UTC anchors and local offsets, making cross-region comparisons reliable and actionable within TimeTether’s analytics surfaces and exports.

Acceptance Criteria
Region Mapping and Aggregation Accuracy
Given an org with users mapped via org metadata and profile locations across 3 regions plus Unassigned When the pipeline runs for a 30-day window Then each user maps deterministically to exactly one region with priority: org metadata > profile location > Unassigned And region-level after_hours_rate and fairness_score are computed and persisted for every region, including Unassigned And metrics are rounded to 4 decimal places and equal the sum of underlying meeting-instance contributions after rounding
Timezone Normalization with UTC Anchors and Offsets
Given meetings spanning multiple time zones with DST changes between 2025-03-01 and 2025-04-15 When metrics are computed Then every metric row includes utc_anchor (ISO 8601 UTC) and local_utc_offset_minutes applicable at meeting start And after-hours classification uses local wall-clock time adjusted by the effective offset (pre/post-DST as applicable) And cross-region comparisons require no additional conversions because anchors are UTC-normalized
Rollups and Segmentation Coverage
Given calendar data containing weekly, biweekly, monthly, and ad-hoc meetings over 90 days When the pipeline executes Then region metrics are produced for 7, 30, and 90-day rollups And each rollup is segmented by cadence (weekly/biweekly/monthly/other), attendee role (host/participant), and meeting_type And counts (numerator/denominator) for each segment are emitted alongside rates and scores And absent segments are emitted with zero counts to maintain a stable schema
Idempotency and Deterministic Re-runs
Given an immutable input snapshot and fixed schema_version and algorithm_version When the pipeline is executed twice Then outputs are byte-identical (values, ordering, rounding) And re-running for a narrower date range recomputes only affected windows and leaves others unchanged And concurrent runs with the same parameters do not produce duplicate records
Historical Backfill from Calendar Ingestion
Given ingestion history from 2023-01-01 through the present When a backfill run is triggered for all regions and 7/30/90-day windows Then metrics are computed for all windows fully covered by data And boundary windows with incomplete coverage are marked completeness=false and excluded from default reads And no duplicate rows exist for the same (region, window_start_utc, window_length_days, segment keys, versions)
Version Tagging and Reproducibility
Given a run configured with schema_version=v3 and algorithm_version=a7 When metrics are written and later queried or exported Then every record includes schema_version, algorithm_version, run_id, and created_at And filtering by both versions returns only records written by that configuration And recomputing with identical inputs and versions yields identical metrics; changing either version writes new records without mutating prior ones
Local Holidays in After-hours Classification
Given Region A has a public holiday on 2025-07-04 and Region B does not When meetings occur on 2025-07-04 at 10:00 local time in both regions Then Region A’s instances are excluded from after-hours penalties per policy while Region B’s are evaluated normally And the holiday source and version are captured in run metadata And if a region lacks a holiday calendar, a warning metric is emitted and non-holiday logic is applied
Drill-Down Explorer and Advanced Filters
"As an engineering manager, I want to drill from regional trends into a specific team or series so that I can identify the meetings most responsible for after-hours inequity."
Description

Provide an interactive explorer that navigates from regions to organizations, teams, meeting series, and individual instances, preserving context via breadcrumbs and sharable URL state. Users can filter by cadence, role, date range, region, work-hour policy version, and fairness score thresholds, with results paginated and performance-optimized for large datasets. The explorer integrates with existing TimeTether entities (teams, series, users) to display linked details and allows one-click pivoting between rollups and time windows. Accessibility (keyboard navigation, ARIA labels) and internationalization are first-class, and client-side caching reduces repeated queries. The outcome is rapid root-cause analysis of inequity drivers.

Acceptance Criteria
Preserve Breadcrumbs and Sharable URL State Across Drill‑Down
Given the user drills Region -> Org -> Team -> Series -> Instance with filters applied (cadence=weekly, role=Eng, dateRange=last 90 days), When they refresh the page, Then the explorer restores the exact path, filters, sort, and current page. Given the user copies the share URL, When opened in a new browser/session, Then it loads the same context within 2 seconds and displays identical breadcrumbs. Given the user changes any filter or drill step, When the state changes, Then the URL updates within 200 milliseconds without full reload and contains no PII (only stable IDs). Given the user uses browser back/forward, Then breadcrumbs, results, and filters stay in sync with the URL.
Advanced Multi-Filter Application and Semantics
Given multiple filters (cadence multi-select, role multi-select, dateRange, region multi-select, policyVersion exact match, fairnessScore >= threshold), When applied together, Then results satisfy ALL filter groups (AND) and within multi-select use OR semantics. Given the fairnessScore threshold control, When set to 0.25, Then only rows with fairnessScore >= 0.25 are returned, inclusive of the boundary. Given a timezone-aware date range (e.g., 2025-06-01 to 2025-08-31), When viewing entities across timezones, Then filtering uses each entity's region work-hours timezone and includes both endpoints. Given filters are active, Then filter chips show counts; When Clear All is clicked, Then all filters reset and results refresh within 1 second. Given no results match, Then an empty state with actionable guidance displays and the URL still encodes the filters.
Pagination, Sorting, and Client-Side Caching at Scale
Given dataset size >= 1,000,000 rows, When loading the first page (page size 50) with filters applied, Then TTFB <= 1000 ms and content becomes interactive within 2000 ms. Given the user navigates to next/previous pages, Then fetch size matches page size, sorting is stable and deterministic across pages, and page-change interaction latency <= 500 ms. Given the user returns to a previously visited page and filter combination within the session, Then data is served from client cache (0 network requests) and renders within 300 ms; cache invalidates on any filter, sort, or policyVersion change. Given the user adjusts page size (50/100/200), Then total counts and page numbers recompute accurately and persist in the URL state.
Entity Linking to Teams, Series, Users With Detail Panels
Given a row representing a Team/Series/User, When clicked or keyboard-activated, Then a right-side details panel opens within 300 ms showing authoritative fields (name, IDs, region, cadence, work-hour policy version, current fairness score, after-hours rate) with deep links to canonical entity pages. Given the details panel is open, When the underlying entity access is forbidden, Then a guarded state is shown with no sensitive fields and a clear message; HTTP 403 is handled without breaking the explorer. Given network failures while loading details, Then the explorer retries up to 2 times with exponential backoff and surfaces a non-blocking error banner; the grid remains usable.
One-Click Pivot Between Rollups and Time Windows
Given the user is viewing a rollup at Region level, When Pivot -> Team is selected, Then the grid switches to Team aggregation preserving filters and sort; totals reconcile within ±0.1% against the previous level. Given a time window selection (weekly, biweekly, monthly), When changed, Then metrics recompute for the new window within 1 second and the selection persists in the share URL. Given pivoting between rollup and instance views, Then column sets adjust accordingly and column order is preserved where columns overlap.
Accessibility: Keyboard Navigation and ARIA Compliance
Given only a keyboard, When navigating the explorer, Then all interactive elements are reachable in a logical tab order, focus is visibly indicated, and Enter/Space activate controls; arrow keys navigate grid cells/rows. Given screen reader use, Then all controls and table headers have accessible names/roles/states via ARIA; breadcrumbs announce the full path; the details panel is modal with a focus trap; Escape closes it and returns focus to the invoking element. Given color contrast requirements, Then text and interactive elements meet WCAG 2.1 AA contrast ratios; no information is conveyed by color alone.
Internationalization and Locale Awareness
Given the app locale is set to fr-FR, Then dates, numbers, and percentages format per locale (DD/MM/YYYY, comma decimal) and translated strings render without truncation; RTL locales mirror layouts appropriately. Given the user switches locale via URL parameter or profile setting, When the explorer reloads, Then it appears in the new locale with identical filters and drill path preserved; week-based windows align to the locale’s week start. Given locale-aware numeric inputs, When entering a fairness threshold using a comma decimal, Then the value parses and validates correctly and applies to filtering.
Metrics Snapshotting, Versioning, and Change Deltas
"As a compliance auditor, I want versioned metric snapshots with annotated change deltas so that I can trace how and why fairness metrics evolved over time."
Description

Introduce scheduled snapshotting of fairness metrics with explicit versioning tied to computation algorithms and policy configurations. Each snapshot captures metric values, inputs (e.g., work windows, rotation rules), and annotations for change reasons with actor attribution. When policies change, the system records pre- and post-change deltas and links snapshots to create a transparent evolution trail. Recomputations create superseding snapshots without deleting prior records, preserving lineage in the immutable ledger. This enables reproducibility of historical views, accurate trend analysis across policy shifts, and precise audit narratives about why metrics moved.

Acceptance Criteria
Scheduled Snapshot Creation Bound to Algorithm Version and Policy Configuration
Given a daily snapshot schedule at 00:05 UTC is active and algorithm_version="v2.3.0" and policy_hash="9f2c3e..." are current When the scheduled time elapses Then exactly one snapshot is created per tenant per schedule window within 2 minutes of 00:05 UTC And the snapshot records snapshot_id (UUIDv4), created_at (UTC ISO-8601), algorithm_version, policy_hash, period_start, period_end, and status="success" And no duplicate snapshot exists for the same tenant+period+algorithm_version+policy_hash
Snapshot Content Completeness and Schema Validation
Given a snapshot is created Then it includes required metrics (after_hours_rate, fairness_score) and region-level breakdowns And it includes inputs: team_work_windows, rotation_rules, timezone_map, cadence, role_filters (nullable), source_data_version And it includes annotations: reason (nullable), actor_id (nullable for automated), actor_type (system|user) And it includes integrity fields: content_hash (SHA-256) and previous_hash for ledger chaining And the snapshot passes schema validation; if any required field is missing, the snapshot is rejected and an error is logged
Policy Change Delta Recording and Snapshot Linking
Given a policy configuration is changed by an authorized user who provides an annotation When the change is saved and the next computation runs Then the system creates linked pre_change_snapshot and post_change_snapshot with a shared change_event_id And for each tracked metric, delta_abs and delta_pct are computed and stored And both snapshots record actor_id and the provided annotation And the ledger API returns the pair in chronological order with a link to view the delta context
Recomputation Produces Superseding Snapshot Without Deleting Prior Records
Given a recomputation is triggered for an historical period with corrected source data and unchanged policy When the recomputation completes Then a new snapshot is stored with supersedes_id referencing the prior snapshot for the same tenant and period And the prior snapshot remains readable and immutable And queries with include_superseded=false return only the latest snapshot per period; include_superseded=true returns the full lineage chain in order
Immutability and Tamper-Evidence Verification
Given any stored snapshot When an attempt is made to update or delete the snapshot via API or console Then the operation is denied with HTTP 403/409 and an audit event is recorded with actor_id, timestamp, and reason And a ledger verification endpoint returns valid=true when content_hash and previous_hash form an unbroken chain; if the chain is broken it returns valid=false and the first invalid snapshot_id
Historical Reproducibility Across Policy Shifts
Given a user requests a historical report pinned to algorithm_version and policy_hash for a date range When the system generates the report Then reported metric totals per region match the stored snapshot values exactly (tolerance=0) And the response includes the list of snapshot_ids used and a reproducibility_token And repeating the same request returns identical values and the same token
Export Includes Versioning, Deltas, and Lineage Metadata
Given a user requests a CSV or Parquet export for a range that includes at least one policy change When the export is generated Then each row includes: tenant_id, region, period_start, period_end, snapshot_id, supersedes_id, algorithm_version, policy_hash, after_hours_rate, fairness_score, delta_abs, delta_pct, actor_id, annotation, content_hash And the export row count matches the number of region-period combinations in the ledger for the range And the export completes within 60 seconds for up to 10,000 rows and is available via a pre-signed URL for at least 24 hours
One-Click CSV/Parquet Export
"As an equity owner, I want one-click exports in analyst-friendly formats so that I can share defensible evidence and run offline analyses without manual cleanup."
Description

Enable one-click export of the current Equity Ledger view, including all applied filters and visible columns, to CSV and Parquet formats. Exports include schema version, data dictionary link, UTC timestamps, and region/timezone metadata for downstream reproducibility. Large exports run as asynchronous jobs with progress feedback, download links, and optional delivery to cloud storage (S3/GCS) or secure share links. PII is minimized and redacted per role-based policies, and export events are logged in the ledger for audit trails. This integrates with TimeTether’s download service and respects user permissions and rate limits.

Acceptance Criteria
One-click export of current view (filters and columns)
Given a user with Export permission is viewing the Equity Ledger with filters applied and a customized visible column set and order When the user clicks Export and selects CSV or Parquet Then the exported dataset contains only rows matching the active filters And contains only the visible columns in the same order And the row count equals the count displayed in the UI for the current view And column headers/names in the export match those shown in the UI
CSV and Parquet format fidelity and metadata inclusion
Given an Equity Ledger export When CSV is selected Then the file is UTF-8 encoded, comma-separated per RFC 4180 with a single header row And all timestamp fields are formatted as ISO 8601 UTC with a Z suffix And a companion .metadata.json with the same basename is provided containing schema_version (non-empty), data_dictionary_url (reachable), generated_at_utc, regions[], and timezones[] When Parquet is selected Then the file contains the same rows and columns as CSV And preserves data types (integers, decimals, booleans, timestamps stored in UTC) And includes key-value metadata entries for schema_version, data_dictionary_url, generated_at_utc, regions, and timezones
Async export for large datasets with progress and downloads
Given the async export threshold is configured to N rows in the environment And the current export is estimated to be greater than or equal to N rows When the user initiates export Then an asynchronous export job is created and visible with statuses Queued → Running → Completed or Failed And progress percentage updates at least every 5 seconds while running And upon completion a downloadable link is available via the TimeTether download service And on failure the user sees an error message and a Retry action that enqueues a new job
Optional delivery to S3/GCS and secure share links
Given the user has an active S3 or GCS connection and permission to deliver exports When the user selects Deliver to S3 and specifies bucket and path Then the system writes the exported artifact to the location with correct Content-Type (text/csv or application/x-parquet) and server-side encryption enabled And returns the object URI And, if requested, generates a pre-signed link with a configurable expiry that downloads the exact artifact When the user selects Generate secure share link Then a time-limited, unguessable download URL is created via the download service And the link expires at the configured time and returns HTTP 403 after expiry
Role-based PII minimization and redaction in exports
Given role-based export policies are configured When an Equity Owner exports Then person-level identifiers (emails, names) are masked to the last 2 characters and user IDs are replaced with stable pseudonymous IDs When an Auditor exports Then only region- and team-level aggregates are present and no person-level identifiers are included When a Team Lead exports Then identifiers are limited to members of their teams with emails masked and no home addresses or phone numbers present When a Standard User attempts export Then access is denied and no file is produced Then the export metadata includes a redaction summary listing fields that were masked or omitted for the current export
Audit logging of export events in Equity Ledger
Given any export is initiated or completed When the event occurs Then an audit record is appended containing export_id, user_id, role, initiated_at_utc, completed_at_utc (if applicable), format, destination (download/S3/GCS/share link), active filters, visible columns, row_count, and outcome (success/failure) And the record includes an integrity hash linking to the previous record to support tamper-evidence And the record becomes queryable within 60 seconds of the event
Permissions and rate limiting enforcement for exports
Given a user without Export permission views the Equity Ledger Then the Export action is not visible in the UI And any direct API attempt returns HTTP 403 and no export job is created Given the rate limit is configured to R exports per U minutes per user When a user exceeds this limit Then further export requests return HTTP 429 with a Retry-After header And the UI displays a descriptive error And no additional jobs are queued while over the limit When under the limit and authorized Then exports proceed normally
Role-Based Access Control and Privacy Safeguards
"As a security administrator, I want role-scoped, privacy-preserving access controls so that the Equity Ledger protects sensitive data while enabling legitimate audits and analysis."
Description

Implement fine-grained RBAC with roles (Equity Owner, Manager, Analyst, Auditor, Admin) and regional scoping to enforce least-privilege access to Equity Ledger data. Sensitive fields are minimized and redacted at source; region-level rollups are viewable without exposing individual identities. Integrations with SSO (SAML/OIDC), SCIM for provisioning, and IP allowlists ensure secure access. All reads/exports are audit-logged with purpose-of-use metadata, and data retention policies align with GDPR/CCPA and customer contracts. Permissions are enforced consistently across UI, API, and export paths to prevent leakage.

Acceptance Criteria
Region-Scoped RBAC Enforcement Across UI, API, and Exports
Given a user with Manager role scoped to Region A, When they view, query, or export Equity Ledger data, Then only Region A data is accessible and all other regions return 403. Given a user with Analyst role scoped to Regions A and B, When calling the API with region=B and any filter, Then the response contains only region=B and excludes A unless explicitly requested and authorized. Given an Admin, When they access any region, Then access is permitted and all access is logged with role=Admin. Given an Auditor with read-only permission, When they attempt to create, edit, or delete settings or exports, Then the action is blocked with 403 and no side effects. Given any user without the Equity Ledger permission, When they hit any Equity Ledger endpoint or UI route, Then they receive 404 or 403 per policy and no data is leaked in errors. Given an export initiated from the UI or API, When the export job runs, Then RBAC filters are applied server-side before file generation and validated in job logs.
Sensitive Field Redaction and Minimization at Source
Given ingestion of meeting data containing participant identities and raw timestamps, When Equity Ledger processing persists records, Then PII/identifiers are hashed or removed and only region-level aggregates and pseudonymous keys are stored. Given any query to the Equity Ledger store, When fields include sensitive identifiers, Then the query engine returns redacted values or omits the fields entirely. Given a user with Admin role, When they attempt to request unredacted fields, Then the system denies access because unredacted fields are not stored. Given debug/logging is enabled, When processing sensitive fields, Then logs do not contain raw PII and pass automated checks for sensitive data patterns.
SSO (SAML/OIDC) and SCIM Provisioning with Role/Region Mapping
Given SAML/OIDC is configured, When a user signs in, Then only IdP-authenticated users can access and local password auth is disabled. Given IdP-sent attributes role and regions, When a user signs in, Then their app role and region scopes are mapped from those attributes and enforced in the session and access token. Given SCIM provisioning, When an account is deprovisioned or role/regions change at the IdP, Then the changes reflect in the app within 5 minutes and any active sessions are revoked within 10 minutes. Given a user attempts access without required claims or outside an allowlisted tenant, When authenticating, Then access is denied and an audit log entry records the failure reason.
IP Allowlist Enforcement Across UI, API, and Exports
Given an organization IP allowlist of CIDR ranges, When a request originates outside all ranges, Then the UI, API, and export downloads return 403 and no bytes of export are streamed. Given a request from an allowed IP, When accessing any Equity Ledger route, Then the request proceeds subject to RBAC. Given an in-progress export download, When the client IP changes outside the allowlist mid-stream, Then the connection is terminated and the event is logged. Given allowlist changes, When an admin updates the ranges, Then new rules take effect within 2 minutes for new requests.
Audit Logging of Reads and Exports with Purpose-of-Use
Given access to Equity Ledger data, When a user initiates a read or export, Then the UI/API requires a purpose-of-use value from a predefined list before proceeding. Given a successful read or export, When the action completes, Then an immutable audit log entry is written including user, role, regions accessed, filters, result counts/size, IP, purpose, timestamp, and correlation ID. Given an attempt to bypass logging via direct storage access or internal API, When the request lacks a valid service token, Then the operation is denied and the attempt is logged. Given audit log verification, When computing the hash chain over a time window, Then the chain validates and any tampering produces a verification failure.
Region-Level Rollups Without Individual Identity Exposure
Given a rollup view for a region, team, or cadence, When results would reveal fewer than k=5 individuals in a subgroup, Then the metric is suppressed or noise-added per policy and no identities are displayed. Given a user drills into a series, When viewing after-hours rates and fairness scores, Then only aggregated metrics at region or team level are visible without names, emails, or unique identifiers. Given a cross-filter by role, When applied, Then the output respects k-anonymity thresholds and does not enable re-identification via small slices.
Data Retention and Deletion Compliance (GDPR/CCPA and Contracts)
Given customer-specific retention settings, When the retention window elapses, Then detailed event data is deleted or irreversibly anonymized, and rollups are recalculated without personal data. Given a data deletion request under GDPR/CCPA, When processed, Then personal data is purged from active stores within 30 days and from backups within 90 days, with completion recorded in an audit log. Given exports generated, When download URLs are created, Then URLs expire within 24 hours and expired URLs cannot be used to retrieve data. Given a legal hold is applied, When retention jobs run, Then data covered by the hold is exempted from deletion until the hold is lifted, with each skip recorded.
Threshold Alerts for Inequity Breaches
"As a product leader, I want real-time alerts when after-hours burdens exceed set thresholds so that we can intervene quickly and reduce inequity."
Description

Provide configurable thresholds for after-hours rates and fairness scores at region, team, and series levels. The system continuously evaluates metrics and emits alerts via email and Slack when breaches occur, including contextual links to the relevant drill-down views and recent changes. Suppression windows, alert deduplication, and per-channel routing reduce noise. Alert events are written to the ledger as a distinct record type for traceability, and suggested remediations (e.g., rotation adjustments) are surfaced where available. This closes the loop from detection to action.

Acceptance Criteria
Configure thresholds by scope and metric
Given an Equity Owner opens Threshold Settings When they set an After-Hours Rate threshold (comparator: >, value: 20%) at Region=APAC and a Fairness Score threshold (comparator: <, value: 0.75) at Team=Payments and Series=Weekly PM Sync and save Then the system persists each threshold with fields: scope, metric, comparator, value, ownerId, effectiveAt And retrieving thresholds immediately reflects the saved values And precedence applies: Series overrides Team, Team overrides Region during evaluation
Continuous breach detection and alert dispatch
Given thresholds exist for Region=EMEA and Team=Platform And the evaluation service is running When the rolling 30-day After-Hours Rate for Team=Platform crosses > 20% Then a breach is detected within 5 minutes and an alert event is created And email is sent to platform-alerts@example.com and Slack message is posted to #equity-alerts And notifications include: scope, metric, observedValue, threshold, comparator, window, firstDetectedAt, breachId
Alert payload contains contextual deep links
Given a breach alert is generated for Series=Weekly PM Sync When recipients open the included Drill-Down link Then they land on Equity Ledger filtered to Series=Weekly PM Sync with time window = last 30 days And the Recent Changes link opens the change log pre-filtered to the series with last 14 days of edits
Noise controls: suppression, deduplication, and routing
Given a suppression window of 2 hours is configured for scope=Team=Platform And a deduplication window of 30 minutes is enabled When two additional breaches occur for the same metric and scope within the suppression window Then no new notifications are sent, but a single ledger record is added with status=suppressed and parentBreachId And if a Slack route is disabled for the scope, only email is sent per routing rules
Ledgering alert events as tamper-evident records
Given an alert event is created When the event is written to the Equity Ledger Then the record type is alert with fields: breachId, scope, metric, observedValue, threshold, comparator, window, channels, recipients, notificationStatus, createdAt, remediationIds And the record is chained with a hash of the previous record and verifiable via the ledger integrity check endpoint And ledger entries are immutable (update attempts are rejected with 409 and require an append-only correction record)
Suggested remediations included with actionable context
Given a fairness score breach for Series=Weekly PM Sync where rotation imbalance is detected When the alert is sent Then the payload includes a recommended rotation adjustment with estimated impact (target fairness score >= 0.8) and a link to Apply Fix And clicking Apply Fix opens the rotation editor pre-populated with the suggestion and requires confirmation
Notification reliability and fallback
Given a breach alert must be sent via email and Slack When Slack API returns a 5xx or times out after 10 seconds Then the system retries up to 3 times with exponential backoff and falls back to email-only And all attempts and outcomes are recorded on the alert ledger record

Breach Register

Automatic catalog of every policy breach and exception with severity, who/what/when, affected regions, and resolution status. Includes owner assignment, SLA timers, and remediation notes that flow into exports. You gain accountability, faster fixes, and audit‑ready proof without chasing scattered threads.

Requirements

Automated Breach Detection & Cataloging
"As a compliance lead, I want every policy breach and exception automatically recorded with full context so that I have a complete, searchable source of truth without manual collection."
Description

Implement real-time detection and automatic logging of every policy breach and approved exception produced by the TimeTether scheduling engine. Each record must capture who/what/when/where (meeting, participants, organizer, policy rule, timestamps, timezone, affected regions, work window context), link to the originating event (recurring series, invite, calendar source), and include normalization for deduplication across series instances. Support backfill from historical scheduling logs, idempotent writes, and a consistent, versioned schema to ensure auditability. The catalog must be searchable and scalable, guaranteeing complete coverage and traceability for compliance and operational review.

Acceptance Criteria
Real-Time Breach/Exception Detection from Scheduling Engine Events
Given the scheduling engine emits a policy-violation event, When the event is processed, Then a breach record is persisted within 10 seconds and marked open with computed severity per policy rule. Given the scheduling engine emits an approved exception event, When processed, Then an exception record is persisted within 10 seconds and flagged as exception (not breach) with the associated policy rule id. Given transient processing errors occur, When retries happen, Then exactly one catalog record exists for the originating event and processing latency P95 remains ≤ 10s. Given the detector service restarts, When events arrive during the restart window, Then no qualifying events are lost and all are eventually cataloged.
Complete Record Capture with Who/What/When/Where Context
Given a breach or approved exception is cataloged, Then the record includes non-null fields: recordId, recordType (breach|exception), severity (for breaches), policyRuleId, policyRuleVersion, organizerId, participantIds[], participantRoles[], meetingInstanceId, recurringSeriesId (if applicable), inviteId (if applicable), calendarSource, detectedAtUtc, occurredAtUtc, participantTimezones[IANA], meetingTimezone[IANA], affectedRegions[], workWindowContext per participant, and hash of participant set. Given an approved exception is cataloged, Then the record includes approverId, approvalReason, approvalTimestampUtc, and exceptionExpiry (if applicable). Given timestamps are stored, Then they are ISO-8601 in UTC with millisecond precision, and all timezone fields use canonical IANA names. Given a record is written, Then links to originating entities (series, instance, invite, calendar source) are populated with resolvable identifiers and pass referential integrity checks.
Idempotent Writes and Deduplication Across Recurring Series
Given the same originating event is delivered more than once, When processed, Then the catalog contains exactly one record identified by a stable idempotency key derived from eventId and calendarSource. Given multiple instances of the same recurring series violate the same policy with the same participant set, When processed, Then the catalog maintains a single normalized parent record keyed by (recurringSeriesId, policyRuleId, participantsHash, calendarSource) and appends occurrence entries for each instance with correct instanceIds and counts. Given the participant set or policy rule version changes for a later instance, When a violation occurs, Then a new normalized parent record is created and linked to subsequent occurrences. Given concurrent processors handle the same event, When both attempt to write, Then exactly one succeeds and the other is a no-op without creating duplicates.
Historical Backfill with Completeness Guarantees
Given a backfill is initiated for a date range, When it completes, Then 100% of breaches and exceptions present in historical scheduling logs for that range are represented in the catalog and pass the same validation as real-time writes. Given a backfill job is re-run for the same date range, When it completes, Then no additional records are created (idempotent) and occurrence counts remain correct. Given a backfill job is interrupted, When it is resumed, Then it continues from the last committed checkpoint without data loss or duplication. Given backfilled and real-time records overlap in time, When both pipelines run, Then the catalog contains no duplicates and all records share the same normalization and keys.
Versioned, Auditable Schema with Immutable History
Given a record is created, Then it includes schemaVersion and writeMetadata (writerService, writeId, writeTimestampUtc) and passes schema validation against the registered version. Given updatable fields (e.g., resolution status external to detection) change, When updates are applied, Then an append-only versioned change is recorded with prior versions retained and queryable; original detection fields remain immutable. Given a schema change is introduced, When the writer deploys, Then schemaVersion increments, backward compatibility is preserved for reads, and migration scripts (if any) leave a complete audit trail. Given an auditor requests evidence, When a record is retrieved with history, Then the system returns the full version chain with cryptographic content hash per version.
Search and Filter at Scale with Performance SLAs
Given a catalog with ≥ 5,000,000 records, When querying by any single indexed field (policyRuleId, severity, organizerId, participantId, affectedRegions, recordType) or time range, Then P95 response time ≤ 800 ms with correct pagination and stable sort. Given a compound filter (time range + severity + policyRuleId + affectedRegions), When executed, Then results are accurate, paginated, and P95 ≤ 1,200 ms for up to 100 pages of 50 items. Given a free-text search on remediation notes and policy rule names, When executed, Then relevant results are returned with highlighting and P95 ≤ 1,200 ms on 5M records. Given an export is requested for the current filtered result set, When generated, Then a CSV and JSON export are available with consistent counts and field order, including occurrence details and links.
Traceability to Originating Events and Calendar Sources
Given a catalog record, When its series, instance, invite, and calendarSource links are resolved via API, Then each returns 200 OK with the expected entity payloads or a tombstone with minimal metadata if the source is deleted. Given an auditor requests end-to-end trace, When provided a recordId, Then the system can retrieve the originating scheduling engine eventId, processing jobId, and all occurrenceIds to reconstruct the timeline. Given a record references external systems, When those systems are unavailable, Then the catalog still returns the stored identifiers and last-known metadata without failing the read. Given a link is corrupted or missing, When integrity checks run nightly, Then the system flags the record and emits an integrity alert with details for remediation.
Policy Mapping & Severity Scoring
"As a security program owner, I want breaches classified against our current policy set with clear severity so that I can prioritize remediation based on risk and regulatory obligations."
Description

Map each breach to a specific policy (ID and version) and compute severity and risk score using configurable rules (e.g., after-hours impact, number of attendees affected, customer-facing vs internal, recurrence). Persist affected regions and regulatory tags (e.g., GDPR, SOC 2 relevance) and expose a severity matrix for admins to tune without code. Show rationale for the assigned severity and maintain historical policy versions to keep past records accurate when policies evolve. This enables consistent prioritization and regulatory alignment across distributed teams.

Acceptance Criteria
Breach mapped to specific policy ID and version at creation
Given a user is creating a new breach record When they attempt to save without selecting a Policy ID and Version Then the save is blocked with a validation error stating both Policy ID and Version are required Given the user selects Policy ID that exists and a valid Version for that policy When they save the breach Then the breach persists with policyId and policyVersion fields stored and visible on the breach view Given a breach is saved with Policy ID P-102 and Version 2.1 When the underlying policy publishes a new Version 2.2 Then the breach continues to reference Version 2.1 and does not auto-update Given an authorized admin edits the breach mapping to a different policy/version When they save changes Then an audit entry records old->new values, editor, timestamp, and reason is required
Severity and risk score computed via configurable rules
Given the Severity Matrix is configured as: after_hours_weight=2; attendees_points={1-5:1, 6-15:2, 16+:3}; recurrence_points={one-off:0, weekly:2, daily:3}; customer_facing_multiplier=1.5; severity_buckets={Low:0-3.4, Medium:3.5-6.9, High:7-9.9, Critical:>=10} When a customer-facing breach occurs after-hours with 6 attendees and weekly recurrence Then riskScore = (2 + 2 + 2) * 1.5 = 9.0 and severity = High and both are persisted on the breach Given the same configuration When a non-customer-facing, not-after-hours, one-off breach with 3 attendees occurs Then riskScore = (0 + 1 + 0) = 1.0 and severity = Low and both are persisted Given the configuration above When two identical breaches are scored Then they produce identical riskScore and severity Given an invalid input (e.g., attendees = -1) When scoring is attempted Then validation fails with a descriptive error and scoring is not persisted
Persist affected regions and regulatory tags
Given the Regions list includes [US, EU, APAC, LATAM, Unknown] and Regulatory Tags include [GDPR, SOC 2, HIPAA, PCI, None] When a user records a breach and selects EU and US regions and tags GDPR and SOC 2 Then the breach persists with regions=[EU, US] and regulatoryTags=[GDPR, SOC 2] and they are visible and filterable Given a user does not know the region When they save the breach Then they must select Unknown or the save is blocked with a validation message Given a user attempts to enter an unrecognized regulatory tag When saving Then the save is blocked and the user is prompted to choose from the allowed list Given a breach with regions and tags is exported When filtering by region=EU and tag=GDPR Then the breach appears in the result set
Admin tunes severity matrix without code
Given a user has Admin role When they open the Severity Matrix settings Then they can edit weights, thresholds, and bucket ranges via form inputs with inline validation (numeric only, ranges non-overlapping, min<=max) Given an Admin changes after_hours_weight from 2 to 3 and saves When a new breach is created after the save timestamp Then the new breach uses after_hours_weight=3 in scoring Given an Admin updates the matrix and saves Then a new configuration version is created with versionId, editor, timestamp, diff summary, and rollback capability Given existing breaches created before the change When the matrix is updated Then existing breaches are not auto-re-scored and a banner offers an Admin action to bulk re-score open breaches only with confirmation Given a non-Admin user attempts to access the Severity Matrix settings Then access is denied (HTTP 403) and no changes are applied
Display severity rationale on breach view
Given a breach has been scored When a user opens the breach details Then the UI shows severity level, numeric riskScore, and a rationale breakdown including each contributing factor, its value, its weight/points, and any multipliers applied, along with the configuration version used Given the example configuration (after_hours_weight=2; attendees_points={1-5:1, 6-15:2, 16+:3}; recurrence_points={one-off:0, weekly:2, daily:3}; customer_facing_multiplier=1.5) When a breach is customer-facing, after-hours, weekly, with 6 attendees Then the rationale displays After-hours:+2; Attendees(6-15):+2; Recurrence(weekly):+2; Customer-facing multiplier:x1.5; Total=9.0; Severity=High Given the breach view is loaded When a user clicks "How was this calculated?" Then the rationale panel toggles without page reload and its contents are copyable and included in audit/export
Maintain historical policy versions on past breaches
Given a breach references Policy ID P-102 Version 2.1 When the policy is updated to Version 2.2 Then the breach continues to display and export P-102 v2.1 and links to the v2.1 document snapshot Given an Admin chooses to remap an older breach from v2.1 to v2.2 When saving the change Then an audit entry records old version, new version, editor, timestamp, and reason; and the breach shows the new version thereafter Given searches and reports are run When filtering by policyVersion=2.1 Then breaches linked to 2.1 are returned even if a 2.2 exists
Export includes mapping, scores, regions, tags, and rationale
Given there are breaches with policy mapping, severity/risk scores, regions, regulatory tags, and rationale When an Admin exports to CSV and JSON Then each record includes breachId, policyId, policyVersion, severityLevel, riskScore, regions, regulatoryTags, configVersionId, rationaleText, createdAt (UTC), updatedAt (UTC) Given a breach has multiple regions and tags When exported to CSV Then multi-valued fields are semicolon-delimited without trailing delimiters and to JSON as arrays Given an export of up to 10,000 rows is requested When executed under typical load Then the file is generated within 5 seconds and encoded as UTF-8 with a valid header row for CSV Given the export completes When a random sample of 10 records is cross-checked against the UI Then field values match exactly
Owner Assignment, SLA Timers & Escalations
"As an operations manager, I want breaches to be assigned with clear SLAs and automated escalations so that issues are resolved quickly without manual chasing."
Description

Auto-assign an owner for each breach based on routing rules (team, region, product area, meeting organizer) with the ability to reassign and add watchers. Start SLA timers at detection, with severity-based deadlines, pause/resume controls for pending approvals, and automatic reminders. Trigger escalations to managers and channels (email/Slack) on impending or actual SLA breaches. All timers must respect the owner’s timezone and regional holidays. This drives accountability and faster resolution while fitting distributed workflows.

Acceptance Criteria
Auto-Assignment via Routing Rules on Breach Detection
Given active routing rules mapping team, region, product area, and meeting organizer to owners and a configured default owner When a new breach is recorded with those attributes Then the owner is auto-assigned within 5 seconds based on the most specific matching rule (multi-field > single-field) And the applied rule identifier and assignment timestamp are recorded in the audit log And if no rule matches, the configured default owner is assigned And the assigned owner is visible in the breach detail via UI and API
Manual Reassignment and Watcher Management
Given a breach with an auto-assigned owner and a user with breach.manage permission When the user reassigns the breach to another valid owner Then the owner field updates immediately without resetting the SLA timer And the reassignment is captured with actor, reason, and timestamp in the audit log Given a breach When the user adds or removes watchers Then the watcher list updates immediately and is retrievable via API And watchers do not gain ownership or SLA responsibility And watchers persist across owner reassignment unless explicitly removed
SLA Timer Start and Severity-Based Deadlines Configuration
Given a configured severity-to-SLA mapping (e.g., Critical=4h, High=24h, Medium=72h, Low=168h) When a breach is detected Then the SLA timer starts at the detection timestamp And the due-at deadline is calculated from the severity’s SLA and stored on the breach And changing the severity recalculates the deadline and records the change in the audit log And resolving the breach stops the timer and freezes the final elapsed time And owner reassignment does not reset the timer or elapsed time
SLA Pause/Resume for Pending Approvals
Given a breach awaiting external approval and a user with pause permission When the user pauses the SLA timer Then elapsed time stops accumulating immediately And no reminders or escalations fire while paused And a pause record with actor, reason, and start time is logged When the user resumes the SLA timer Then the deadline shifts by the total paused duration And the pause record is closed with end time and total paused duration And multiple pause/resume cycles are supported with cumulative paused time excluded from SLA
Automated Pre-Breach Reminders
Given reminder thresholds configured at 75% elapsed and 30 minutes before deadline When the SLA timer reaches a configured threshold and the breach is unresolved and not paused Then reminder notifications are sent to the current owner (and watchers if configured) within 60 seconds And reminders are delivered via the selected channels and recorded in the activity log And duplicate reminders for the same threshold are suppressed When the breach is resolved before a threshold fires Then all pending reminders are canceled
Escalations to Managers via Email and Slack
Given escalation rules configured for impending breach at 15 minutes before deadline and for actual breach at or after the deadline When those conditions occur and the breach is unresolved and not paused Then escalation notifications are sent to the owner’s manager and to configured email and Slack channels within 60 seconds And each escalation stage produces at most one notification per breach (deduplicated) And escalation events are recorded with recipients, channels, and timestamps When the breach is resolved Then no further escalations are sent
Owner Timezone and Regional Holiday Handling
Given an owner with timezone TZ_X and regional holiday calendar CAL_X When a breach is detected Then all SLA calculations and timestamps are based on TZ_X And elapsed time that overlaps dates in CAL_X is excluded from SLA accumulation And reminders and escalations scheduled during holidays are deferred to the next non-holiday time in TZ_X When ownership changes to a user with timezone TZ_Y and calendar CAL_Y Then future calculations switch to TZ_Y and CAL_Y from the reassignment time forward And daylight-saving transitions are handled without losing or double-counting elapsed hours
Remediation Notes & Immutable Audit Trail
"As an auditor, I want a complete, immutable history of breach handling with remediation evidence so that I can verify compliance without relying on tribal knowledge."
Description

Provide structured remediation notes (root cause, short-term fix, long-term action, evidence) with attachments and links to related meetings, rules, and tasks. Record a tamper-evident history of every change (who, when, what) including status transitions, SLA pauses, and reassignment. Support closure checklists by severity and lock records post-closure with controlled re-open workflows. Ensure all audit fields are exportable for external audits. This produces audit-ready proof and maintains institutional memory.

Acceptance Criteria
Capture Structured Remediation Notes with Links and Attachments
Given an open breach record and a user with Edit permissions When the user saves remediation notes Then the following fields are required and non-empty: root_cause, short_term_fix, long_term_action, evidence_summary And up to 10 attachments may be uploaded with allowed types: pdf, docx, xlsx, png, jpg, txt and max size 25 MB per file And each attachment is stored with filename, size, SHA-256 checksum, uploader, and upload timestamp (UTC ISO 8601) And the user may add 0–20 links each to meetings, rules, and tasks; each link must resolve to an existing entity and be stored with entity type and ID And the record saves successfully and displays the saved author and saved_at timestamp (UTC ISO 8601) And attempting to save with any missing required field or invalid link returns a validation error without partial save
Immutable, Tamper‑Evident Audit Trail for Field Changes
Given a breach record exists When any field within remediation notes, status, SLA, owner, or links is created, updated, or deleted Then an audit log entry is appended capturing: actor (user ID), action (create/update/delete), field name(s), previous value, new value, and timestamp (UTC ISO 8601) And audit entries are immutable: no user role can edit or delete them And each entry includes a hash and previous_hash forming a verifiable chain; a Verify Audit API returns valid=true for an untampered chain And the UI exposes a read-only audit timeline sortable by timestamp and filterable by actor, action, and field And any attempt to modify or purge audit entries is blocked and logged as a security event
Status Transitions, SLA Pauses, and Reassignments Logged and Enforced
Given a breach record with an active SLA target based on severity When the status changes between Allowed States [Open, In Progress, Waiting on External, Pending Review, Closed] Then the transition is validated against the allowed state diagram and a reason is required for transitions to Waiting on External and Closed And an audit entry records from_state, to_state, reason, actor, and timestamp When an SLA pause is initiated (only from Waiting on External) Then the system requires a pause_reason, starts a pause timer, and logs pause_start; resuming logs pause_end and total_paused_duration is excluded from SLA calculations When the record owner is reassigned Then the new owner is required to acknowledge within 24 hours; acknowledgment (or auto-escalation) is logged with timestamp And the SLA remaining time updates immediately and is visible in the record header
Severity‑Based Closure Checklist Enforcement
Given a breach record with severity in {Low, Medium, High, Critical} When a user attempts to transition status to Closed Then the system loads the severity-specific checklist configuration and requires all items to be checked with evidence (text and/or attachment) for items marked requires_evidence=true And all linked tasks marked as Remediation must be in state Done And a final remediation summary (<= 2000 chars) is required and stored And the close action is blocked with actionable errors if any checklist item is incomplete, evidence missing, or tasks not Done And upon successful close, a closure_certificate object is generated with: closed_by, closed_at (UTC ISO 8601), severity, checklist_version, checklist_items_status, and attached to the record
Post‑Closure Record Lock and Controlled Reopen Workflow
Given a breach record is Closed Then the record becomes read-only for all fields except Reopen Request When a user with role in {SecurityAdmin, ComplianceOfficer} submits a Reopen Request with reason (min 15 chars) and supporting evidence Then the system creates a reopen_case, logs the request, and sets status to Pending Review (not Open) while preserving closure_certificate When the reopen_case is approved Then status changes to In Progress, SLA timers resume from time-of-approval, and a new remediation phase starts while retaining prior audit history And all reopen actions and approvals/denials are recorded in the audit trail and linked to the prior closure And direct edits to closed records without an approved reopen are blocked and logged
Comprehensive Audit Export with Attachments Manifest
Given a compliance user requests an export for a date range and optional filters (severity, region, status) When the export is generated Then a ZIP is produced containing: audit.csv, remediation_notes.json, transitions.csv, sla_events.csv, reassignment.csv, links.csv, closure_certificates.json, and attachments/ files referenced by a manifest.json And all timestamps are UTC ISO 8601; IDs are stable UUIDs; CSVs use RFC 4180; JSON is UTF-8 and JSON Lines when multiple records And the export includes a top-level checksum.txt with SHA-256 hashes for each file and an overall manifest hash And exports of >10,000 records are processed asynchronously with progress and a downloadable URL that expires in 7 days And exporting requires Compliance role and is fully covered by access logging
Referential Integrity for Related Meetings, Rules, and Tasks; Evidence Handling
Given a user adds or edits links to related meetings, rules, or tasks When the user saves the record Then each link is validated against the source system API; unresolved or deleted targets produce a clear validation error and block save And linked items display title and status; if a linked item later becomes unavailable, the record shows a Broken Link badge and the audit trail logs the change When evidence files are uploaded Then they are virus-scanned, stored with checksum, MIME type, and size; disallowed types are rejected with an error; downloads are permitted only to authorized roles And attempting to remove an evidence file creates an audit entry and requires a reason; the original file remains retained per retention policy with a tombstone record
Role-Based Access & Privacy Controls
"As a data protection officer, I want strict access controls and redaction on breach records so that sensitive participant data is protected while we remediate issues."
Description

Enforce role-based access with least privilege, including region-scoped and team-scoped permissions. Support field-level controls and PII minimization for attendee data, with redaction in exports when required. Integrate with SSO/SCIM for provisioning and apply data residency constraints per region. Maintain an access log for all views and edits. This protects sensitive user information while enabling the right stakeholders to act on breaches.

Acceptance Criteria
Least-Privilege Role Enforcement on Breach Records
Given the roles Org Admin, Security Analyst, Regional Manager, Team Lead, and Auditor are defined When a user with each role attempts actions [view, create, update, delete, export, configure_rbac] Then permissions are enforced as follows: - Org Admin: allow all actions - Security Analyst: allow [view, create, update, export]; deny [delete, configure_rbac] - Regional Manager: allow [view, update] within assigned regions; deny [create, delete, export, configure_rbac] - Team Lead: allow [view] and [update: resolution_status, remediation_notes, owner] within assigned teams; deny others - Auditor: allow [view] only; deny others And unauthorized UI controls are hidden/disabled And unauthorized API calls return HTTP 403 with error code RBAC_FORBIDDEN And all allows/denies are covered by automated tests per role-action matrix
Region-Scoped Visibility and Residency Constraints
Given a user scoped to regions = [EU] When they query or browse the Breach Register Then only breaches whose affected_regions intersect [EU] are returned And breaches with affected_regions that do not include EU are not returned And API list/detail endpoints filter consistently with UI Given an export is created for EU-scoped data Then the export job runs on EU-hosted infrastructure and is stored in an EU-region bucket And the export artifact metadata includes data_region = EU and residency_verified = true And cross-region replication is disabled for the artifact And attempts to generate EU exports from non-EU processing nodes are rejected with HTTP 403 DATA_RESIDENCY_BLOCK
Team-Scoped Permissions and Cross-Team Isolation
Given a Team Lead with team_scope = [Team A] When viewing breach records Then only records where responsible_team ∈ [Team A] or reporter_team ∈ [Team A] are visible And records for other teams are neither listed nor retrievable by direct URL/API Given the same user attempts to set owner to a user outside [Team A] Then the update is blocked with HTTP 403 RBAC_SCOPE_VIOLATION and the UI shows an inline error Given a user manipulates client-side filters to request another team’s record Then the server enforces team scope and returns 403 without leaking record existence
Field-Level PII Controls and Minimization
Given PII fields are defined as [attendee_email, attendee_full_name, attendee_phone] When a user without permission PII_VIEW accesses a breach record Then those fields are masked in UI and redacted in API responses (e.g., email = e***@example.com) And masked/redacted values are consistent across list, detail, and search endpoints Given a user with permission PII_VIEW accesses the same record Then unmasked values are returned/displayed And the response header includes pii-exposure: true and fields list And a PII access event is written to the audit log Given a search query is executed by a user without PII_VIEW Then search is performed on hashed/normalized tokens and results do not reveal raw PII
Export Redaction by Role and Region
Given any user initiates an export of breach records When the dataset contains PII Then redaction is enabled by default and PII fields are redacted in the export file And only users with permission PII_EXPORT can disable redaction via an explicit include_pii flag And attempts to set include_pii=true without PII_EXPORT are rejected with HTTP 403 PII_EXPORT_FORBIDDEN Given the dataset includes EU-region records Then include_pii=true is rejected unless compliance_override=true and the requester has role Org Admin And the export manifest records redaction_policy, requester_id, timestamp, and override_reason (if any) And a preview sample shows the applied redaction pattern before download
SSO/SCIM Provisioning and Deprovisioning of Roles and Scopes
Given SCIM is enabled and the IdP provisions a user with attributes role=Security Analyst, regions=[EU], teams=[Team A] When provisioning occurs Then the user appears in the application with the mapped role and scopes within 60 seconds And first-time SSO login succeeds without manual invite Given the IdP updates the user to regions=[EU, US], teams=[Team B] Then scope changes take effect within 60 seconds for API and UI authorization decisions Given the IdP deprovisions or disables the user Then all sessions are revoked within 5 minutes and subsequent requests return 401/403 And SCIM events are idempotent and safe to replay without duplicating users
Immutable Access Logging for Views, Edits, and Denials
Given any user views a breach record via UI or API When the record payload is returned Then an audit log entry is written with actor_id, role, action=view, record_id, fields_accessed (PII flagged), timestamp_utc, ip, and user_agent Given any user edits a breach record Then the audit log includes before/after values for changed fields with PII masked to last 2 characters And the log entry stores action=update, record_id, fields_changed, timestamp_utc, and correlation_id Given an unauthorized attempt occurs (403) Then an audit entry is created with action=deny, reason=forbidden, target=record_id or endpoint, and no sensitive payload And audit logs are append-only, tamper-evident, queryable by actor/date/record, and exportable for audits
Filtering, Dashboards & SLA Reporting
"As a department lead, I want dashboards and filters that surface breach trends and SLA performance so that I can allocate resources and measure improvements."
Description

Offer advanced filtering and saved views across severity, status, owner, policy, region, team, timeframe, and recurrence. Provide dashboards for backlog, MTTR, SLA attainment, aging by severity, and after-hours reduction over time, with drill-down to records. Allow export of charts and underlying data and embed widgets in TimeTether’s home and team pages. This delivers situational awareness, trend analysis, and operational KPIs for leaders.

Acceptance Criteria
Multi-Field Filtering and Combined Conditions
Given I am on the Breach Register list view with records spanning severities, statuses, owners, policies, regions, teams, timeframes, and recurrence values When I apply filters: Severity ∈ {Critical, High}, Status = Open, Owner = Current User, Policy = "Data Retention", Region ∈ {EU, US}, Team = "Payments", Timeframe = Last 90 days, Recurrence = Recurring Then only records matching all selected conditions are displayed And the result count equals the number of displayed records And the query completes in ≤ 2 seconds for datasets up to 50,000 records And a clear empty-state message appears when zero results are returned with a one-click "Clear Filters" action And all active filters persist on page refresh and are encoded in the URL for shareable deep-links
Saved Views Creation, Sharing, and Defaulting
Given I have applied a set of filters, column selections, sort order, timeframe, and recurrence When I save them as a view named "EU Critical Backlog" with scope = Team Then the view captures filters, columns, sort, timeframe, and recurrence exactly as configured And the view appears in the Saved Views menu for all team members with read access And the owner can rename, update scope, and overwrite the saved settings And I can set this view as my default; team admins can set a team default without overriding personal defaults And deleting a saved view removes only the view metadata, not any breach records And private views are visible only to the creator; team views are read-only to others unless granted edit rights And the deep-link to the view restores the same results and UI state
SLA Attainment and MTTR Dashboard Cards with Drill-Down
Given policy SLA definitions exist and breach SLA timers track start, pause, and stop states When I open the dashboard with filters applied (e.g., Region = EU) Then cards display: Backlog (count of open breaches), MTTR (median and mean time-to-resolution for last 30 and 90 days), and SLA Attainment (% resolved within SLA for last 30 and 90 days) And the metrics reflect the current filter context and update within 2 seconds for up to 50,000 records And MTTR excludes paused durations; SLA calculations exclude approved exceptions and respect pauses And clicking any card drills down to the list view pre-filtered to the contributing records And computed values match backend reference calculations within ±1 minute (MTTR) and ±0.1 percentage points (SLA)
Aging by Severity and After-Hours Reduction Trend
Given aging buckets are configured as 0–7, 8–14, 15–30, 31–60, and 61+ days When I view the Aging by Severity chart Then it shows counts and percentages of open breaches per severity within each bucket, with legends and tooltips And clicking a bar drills down to the corresponding filtered breach list Given team work windows and user timezones are defined When I view the After-Hours Reduction trend for the last 6 months Then it displays monthly % of resolutions occurring after-hours vs within work windows and the net reduction from baseline And calculations use each assignee’s local timezone, correctly handling daylight saving transitions And both charts render within 2 seconds and honor the current global filters
Export Charts and Underlying Data with Filters Applied
Given I have a dashboard or chart with active filters When I choose Export Then I can export the visualization as PNG and SVG (2x and 3x resolution options) And I can export the underlying dataset as CSV or JSON And exported files include the chart/title, applied filters, timezone, generated-at timestamp, and data source/version metadata And exports respect permission boundaries so only viewable records are included And large exports (>100k rows) run as an async job with progress indication and a secure download link upon completion (email notification optional) And exported aggregates match on-screen metrics within ±0.1 percentage points and raw timestamps are in ISO 8601 with timezone offsets
Embeddable Widgets on Home and Team Pages with Permissions
Given I have permission to configure widgets When I add the Breach Backlog and SLA Attainment widgets to the TimeTether home and team pages Then widgets render responsively in the allocated slots and inherit page context (e.g., team, timeframe) And widget configuration allows selecting metric, timeframe, and additional filters, which persist per page And clicking a widget navigates to the corresponding dashboard or filtered list view And widgets load in ≤ 1 second at p95 and show a non-blocking error state with retry on failure And widgets never display PII or restricted fields and always enforce the current user’s access controls
Exports, APIs & Webhooks
"As a platform engineer, I want robust exports, APIs, and webhooks so that I can integrate the breach register with our SIEM and GRC systems automatically."
Description

Support on-demand and scheduled exports (CSV/JSON) including remediation notes and audit fields, with column selection and filters. Provide secure APIs to list, query, and update breach records, plus webhooks for breach.created, breach.updated, breach.closed, and sla.breached events with retry and signature verification. Include rate limits, OAuth scopes, and schema versioning. This enables integration with SIEM/GRC tools and downstream reporting without manual effort.

Acceptance Criteria
On-Demand CSV Export with Column Selection and Filters
Given a user with OAuth scopes exports:read and breaches:read selects Export Now with format CSV, columns [id, title, severity, region, remediation_notes, created_at, updated_at, owner_id, status, sla_due_at], and filters severity in [High, Critical], region = EMEA, created_at within last 30 days When the user confirms the export Then the system generates a downloadable CSV within 60 seconds And the filename follows pattern breaches_YYYYMMDD_HHMMSS.csv And the CSV contains a single header row exactly matching the selected columns and order And every data row satisfies all selected filters And remediation_notes and audit fields (created_at, updated_at) are populated when available; empty fields are blank, not null literals And values containing commas, quotes, or newlines are correctly quoted per RFC 4180 And an empty result set still returns the header row with zero data rows
Scheduled JSON Export with Filters and History
Given a user with OAuth scopes exports:write and breaches:read schedules a daily export at 01:00 UTC with format JSON, filters region in [AMER, EMEA] and status != closed, and selects columns [default] When the scheduled time occurs Then the export job executes within 5 minutes of the scheduled time And a JSON file is generated containing only records matching the filters and including remediation_notes and audit fields And the export appears in Export History with status Completed, execution timestamp, file size, and a retention expiry date And the user receives a notification that links to the export file And if no records match, the JSON is a valid empty array [] with schemaVersion included And if generation fails, the job status is Failed with an error code and is retriable by the user
Breach List API: Querying, Filtering, and Pagination
Given a client holds a valid OAuth token with scope breaches:read When it calls GET /api/breaches?severity=High&region=APAC&limit=50&cursor=<token> Then the API responds 200 within 2 seconds with a JSON array of up to 50 breach records And only records matching the filters are returned And each record includes remediation_notes and audit fields (created_at, updated_at, created_by, last_modified_by) And pagination metadata includes next_cursor when more results exist and is absent when at end And invalid filter values return 400 with a machine-readable error code and message And missing/invalid token returns 401; insufficient scope returns 403 And responses include rate limit headers X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After when applicable
Breach Update API: Secure Partial Updates with Webhook Emission
Given a client with OAuth scope breaches:write sends PATCH /api/breaches/{id} with a JSON body updating allowed fields (e.g., status, owner_id, remediation_notes) When the request is valid and the breach exists Then the API responds 200 with the updated resource including updated_at and last_modified_by reflecting the caller And changes are persisted and visible on subsequent GET requests And a breach.updated webhook is enqueued with the new values and previous values where applicable And attempts to modify immutable fields (id, created_at) return 400 with an error code immutable_field And updating a non-existent id returns 404 And requests exceeding payload size limits return 413 And requests without sufficient scope return 403
Event Webhooks: Delivery, Retries, and Signature Verification
Given a subscribed endpoint is configured with a shared secret and is enabled for events breach.created, breach.updated, breach.closed, and sla.breached When one of these events occurs Then a POST is delivered within 30 seconds to the endpoint with headers X-TT-Event, X-TT-Id, X-TT-Timestamp, and X-TT-Signature (HMAC-SHA256) And the payload contains eventType, schemaVersion, occurredAt, and the breach object with remediation_notes and audit fields And if the endpoint responds with non-2xx, the system retries at least 3 times with exponential backoff up to 10 minutes total And duplicate deliveries carry the same X-TT-Id for idempotency And deliveries older than 5 minutes are rejected by the receiver when the signature timestamp is stale And invalid or missing signatures cause the receiver verification to fail; such attempts are logged and not counted as successful deliveries
Rate Limiting and Throttling Behavior
Given a client makes requests to the APIs When the request rate exceeds 600 requests per minute per OAuth client Then subsequent requests receive HTTP 429 with a JSON error body and a Retry-After header indicating the wait time And responses include X-RateLimit-Limit and X-RateLimit-Remaining reflecting the current window And normal operation resumes automatically after the window resets without manual intervention And webhook delivery is not throttled by API client rate limits
OAuth Scopes and Schema Versioning Negotiation
Given endpoints and webhooks support schema versioning When a client sends Accept: application/vnd.timetether.breaches+json;version=2 to an API endpoint Then the response conforms to schema version 2 and includes header X-Schema-Version: 2 And omitting version negotiates the default GA version documented for the API And requesting an unsupported version returns 406 with a version_not_supported error code And export files (CSV/JSON) embed schemaVersion metadata in the file (JSON field; CSV metadata row or filename suffix) And access to exports requires exports:read and creating schedules requires exports:write; listing/updating breaches requires appropriate breaches:* scopes

Score Timeline

Time‑series trends for fairness scores and after‑hours rates with event annotations (DST shifts, rotation changes, headcount moves). Compare periods, set targets, and export the deltas for quarterly reviews. Makes it easy to prove improvements and explain inflection points to stakeholders.

Requirements

Time-Series Metric Aggregation
"As an analytics-minded engineering manager, I want accurate daily and weekly fairness and after-hours metrics so that I can track trends and quantify improvements over time."
Description

Implement a scalable pipeline to compute and store daily and weekly fairness scores and after-hours meeting rates per team, cohort, timezone, and meeting series. Handle historical backfill with idempotent runs; respect configured work windows and daylight saving transitions; persist results as immutable time-series with calculation version and source lineage. Expose a query API supporting filters (team, cohort, metric, date range), pagination, and aggregation. SLA: metrics update within two hours of schedule changes; support targeted recomputation on configuration or rotation rule changes.

Acceptance Criteria
Daily and Weekly Metric Computation Across Dimensions
- Given meeting schedules and configurations for a date range D, When the pipeline runs, Then it emits daily metrics fairness_score and after_hours_rate for each of team, cohort, timezone, and meeting_series. - Then weekly aggregates are produced for the same dimensions aligned to ISO-8601 week boundaries. - Then each record includes fields: metric, granularity (daily|weekly), entity_type, entity_id, period_start, period_end, value, unit, calculation_version. - And values are numeric with up to 4 decimal places of precision.
Backfill and Idempotency
- Given a historical backfill over range R is executed twice with the same parameters and calculation_version, When the second run completes, Then the set of output records is identical and no duplicates exist. - And re-running with a different source_run_id but the same calculation_version does not create duplicates for the same natural keys. - And partial failures can be resumed without duplicating or skipping periods (checkpointing ensures exactly-once per natural key).
Work Windows and DST Compliance
- Given participants in IANA timezones with configured work windows, When computing after_hours_rate for a day with a DST transition, Then classification uses each participant's local offset at meeting start time. - Then meetings starting exactly at a work-window boundary are counted as within-hours; meetings outside are counted as after-hours. - And for a provided DST fixture set spanning at least three regions, computed after-hours flags match expected results 100%.
Immutable Time-Series with Versioning and Lineage
- Given an existing metric record identified by (entity_type, entity_id, metric, granularity, period_start, calculation_version), When a new run with the same calculation_version occurs, Then no overwrite of existing records is permitted (append-only enforced by unique constraint). - When calculation_version changes, Then new records are appended and prior version records remain unchanged. - Then each stored record has non-null lineage fields: calculation_version, source_run_id, computed_at, input_snapshot_id.
Query API: Filtering, Pagination, and Granularity
- Given stored metrics, When the API is called with filters team, cohort, metric list, and date_range [start,end], Then only matching records are returned. - When page_size=500 is specified, Then responses contain at most 500 records and include next_cursor when additional pages exist. - When iterating with next_cursor until exhaustion, Then all records are returned exactly once in deterministic order by period_start ascending, then entity_type, then entity_id. - When granularity is requested as daily or weekly, Then only the requested granularity is returned. - When version is omitted, Then the latest calculation_version per natural key is returned; when version is specified, only that version is returned.
SLA: Metrics Update Within Two Hours of Schedule Changes
- Given a schedule change occurs at time T affecting team X, When the pipeline runs, Then all impacted metrics for team X are recomputed and available via the Query API no later than T+2 hours. - And the operational metric metric_update_latency_seconds shows 95th percentile <= 7200 seconds over a rolling 7-day window.
Targeted Recomputation on Configuration or Rotation Rule Changes
- Given a change to work windows or rotation rules scoped to team X effective date E, When recomputation is triggered, Then only metrics for impacted entities and dates >= E are recomputed. - Then unaffected entities and date ranges remain unchanged (same calculation_version and values). - Then recomputed records have incremented calculation_version and lineage.source_run_id referencing the triggering change event. - And recomputed metrics are available via the Query API within 2 hours of the change.
Event Annotation Framework
"As a product lead, I want key operational events annotated on the charts so that I can explain inflection points without guessing."
Description

Provide a system to capture and render event annotations across the timeline, including automatic events (DST changes by region, fairness/rotation rule updates, headcount joins/leaves) and manual events (policy changes, launches). Events include type, timestamp or span, affected cohorts, description, and source. Deliver APIs and UI to create, edit, and delete annotations; deduplicate overlapping events; support time zone–aware rendering and sticky labeling; enable deep links from chart markers to event detail. Include permissions and audit trail for changes.

Acceptance Criteria
Manual Annotation Creation via UI
Given I am an Editor or Admin viewing the Score Timeline When I create a manual annotation providing type, timestamp or time span, affected cohorts, description, and source Then the system validates required fields and prevents save if any are missing, highlighting the missing fields And when all required fields are valid and I click Save Then the annotation is persisted, assigned a unique event ID, and an audit entry is recorded with actor, timestamp, and payload hash And the annotation renders on the timeline within 2 seconds of save at the correct temporal position in my local time zone And the label remains sticky during pan/zoom and does not overlap adjacent labels (minimum 8px separation)
Automatic Event Generation (DST, Rotation Rule Updates, Headcount Changes)
Given the workspace has members across regions and active scheduling rules When a DST shift occurs for any region with at least one active member OR a fairness/rotation rule is updated OR a member joins/leaves a cohort Then the system creates an automatic annotation with type (DST Shift | Rule Update | Headcount Change), precise timestamp or span, inferred affected cohorts, templated description, and source=system And duplicate automatic annotations for the same event (same type, region/cohort, and timestamp/span) are not created And each automatic annotation is emitted and visible within 5 minutes of the triggering event And the marker renders with a tooltip showing both the event locale time and the viewer’s local time
Edit and Delete with Versioning and Audit Trail
Given I am an Editor or Admin and an annotation exists When I edit any of its fields (type, timestamp/span, cohorts, description, source) and save Then the system persists the change as a new version and records an immutable audit log entry containing before/after diff, actor, timestamp, and source (UI/API) And the event’s deep link (eventId) remains stable and resolves to the latest version When I delete the annotation Then the event is soft-deleted, removed from the timeline within 2 seconds, and an audit entry records the delete action And navigating the existing deep link shows a non-rendered state with access to the audit history
Events API Create/Read/Update/Delete with Concurrency Control
Given I am authenticated with a token that has Editor or Admin scope When I POST /events with a valid payload including type, timestamp or span, cohorts, description, and source Then the API returns 201 Created with id, createdAt, updatedAt, and version When I GET /events filtered by time range and cohorts up to 200 results Then the API returns 200 with results within 500 ms (p95) and accurate filtering When I PATCH /events/{id} or DELETE /events/{id} with If-Match of the last known version Then the API applies the change and returns 200/204; otherwise returns 409 on version mismatch And all write operations generate corresponding audit entries
Deduplication of Overlapping Events
Given multiple annotations share the same type and identical affected cohorts and their timestamps/spans overlap When the system ingests or updates these annotations Then it deduplicates them into a single canonical annotation with a unioned time span, mergedIds list, and mergedSources preserved And the timeline renders a single marker/label with a tooltip indicating the number of merged events And GET /events returns only the canonical annotation for that range by default, with an option includeMerged=true to return children
Permissions and Access Control Enforcement
Given role-based permissions: Admin and Editor (create/edit/delete), Viewer (read-only) When a Viewer attempts to create, edit, or delete via UI or API Then the UI blocks the action (controls disabled) and the API returns 403 forbidden with a stable error code And access denials are logged with actor, endpoint, and reason without storing sensitive request payloads When an unauthenticated request is made Then the API returns 401 and does not disclose the existence of specific event IDs
Deep Links from Timeline Markers to Event Detail
Given an annotation is visible on the timeline When I click its marker or label Then the application opens the event detail panel and updates the URL with a shareable deep link including eventId and timeRange When the deep link is opened in a new session Then the timeline loads the specified range, centers and highlights the event, and shows the latest version’s details If the event was deleted Then the deep link loads the timeline and displays a clear "deleted event" state with a link to audit history
Interactive Score Timeline Visualization
"As a distributed team lead, I want an interactive view of score trends so that I can quickly explore patterns and communicate insights."
Description

Build a responsive, accessible charting component that renders time-series lines for fairness scores and after-hours rates with zoom, pan, and range presets (7/30/90/365 days). Provide tooltips with per-point values and event badges; toggles for metrics, cohorts, and smoothing; overlay of target bands; keyboard navigation and WCAG AA contrast. Ensure performance up to 5k points per series and graceful handling of missing data gaps. Make the component embeddable in team dashboards with theming and dark mode, SSR-friendly, and capable of exporting the visible area as PNG/SVG.

Acceptance Criteria
Responsive chart with zoom, pan, and range presets
- Given the component is mounted with fairness and after-hours data and the viewport is between 320px and 1920px width, When the window is resized, Then the chart resizes without horizontal scrollbars or clipped axis labels and maintains at least 24px padding around the plot area. - Given range presets 7, 30, 90, and 365 days are visible, When the user selects a preset, Then the x-axis domain updates to the selected range and the visible data, target bands, and annotations update accordingly. - Given the chart is in any range, When the user drags horizontally (mouse or touch) to pan, Then the plot shifts accordingly, clamped to data bounds, and a Reset View control appears and restores the last selected preset when activated. - Given the chart has focus, When the user uses mouse wheel with Ctrl/Cmd or a pinch gesture, Then the chart zooms centered on the pointer/gesture midpoint with a minimum zoom showing at least 1 day and a maximum zoom showing the full selected preset. - Given the chart has focus, When the user presses + or - keys, Then the chart zooms in or out one step; pressing 0 resets to the selected preset.
Tooltips and event annotations
- Given the cursor hovers over or focus moves to a time position with data, When the nearest data point is within an 8px hit radius, Then a tooltip appears showing timestamp, fairness score, and after-hours rate for all visible cohorts using consistent number formats and current theme. - Given the timeline includes event annotations (DST shifts, rotation changes, headcount moves), When the time cursor overlaps an event, Then the tooltip lists event badges with type, label, and exact timestamp. - When multiple events fall within 10px on the x-axis, Then badges are grouped into a summarized badge with a count; clicking expands to show the individual events. - Given keyboard focus lands on an annotation marker, When Enter or Space is pressed, Then an event details popover opens and is dismissible with Escape. - Given missing data for a series at a timestamp, When hovering or focusing that time position, Then the tooltip shows No data for that series and does not interpolate values.
Metric, cohort, and smoothing toggles
- Given metric toggles are visible, When the user toggles Fairness or After-hours metrics, Then corresponding lines, legends, tooltips, target bands, and exports include only selected metrics; at least one metric remains selected. - Given cohort checkboxes provided via props, When the user selects or deselects cohorts, Then only selected cohorts' series render; at least one cohort remains selected. - Given smoothing options Off, 7-day, and 14-day are available, When the user selects a smoothing option, Then the chart applies the smoothing to visible series and updates legends with smoothed indicators; raw data points remain accessible via a Raw toggle in the tooltip. - Given a cohort is toggled off and on again, When it reappears, Then its color assignment remains consistent with previous renders.
Target band overlay
- Given target bands (lower and upper thresholds) are provided per metric, When the chart renders, Then shaded target bands appear for each selected metric with legend labels and accessible names. - When a metric is toggled off, Then its target band hides; when toggled on, it reappears in the correct position. - When a target band only partially intersects the visible range, Then only the intersecting portion is rendered. - When a data point is within the visible range, Then the tooltip indicates whether the point is Inside or Outside the target band for that metric and shows the delta to the nearest bound.
Accessibility and keyboard navigation (WCAG AA)
- Given light and dark themes, Then all chart colors (lines, text, grid, target bands, annotations) meet a minimum 4.5:1 contrast ratio against their background; focus indicators meet 3:1 against adjacent colors. - All interactive controls (presets, toggles, export, reset, annotations) are reachable via Tab/Shift+Tab in logical order, have visible focus outlines, and include ARIA labels and roles. - Given the plot area has focus, When the user presses Arrow Left/Right, Then the view pans by 10% of the current range; with Shift+Arrow, it pans by 25%; Home jumps to the oldest bound; End jumps to the newest bound. - Given the plot area has focus, When the user presses + or - keys, Then the chart zooms by 20% steps; pressing 0 resets zoom to the selected preset. - Tooltip updates triggered by keyboard focus changes are announced via an ARIA live region with metric names, cohort names, timestamp, and values; annotation markers are programmatically associated with their timestamps. - Automated accessibility testing (axe) reports zero serious or critical violations on the component in both themes.
Performance and missing data handling
- Given up to 2 metrics x 5 cohorts x 5,000 points per series (max 50,000 points), Then initial render completes within 500ms in Chrome and 800ms in Safari on a mid-tier laptop (4-core CPU, 8GB RAM) with no console errors. - During pan and zoom interactions on the maximum dataset, Then median frame rate is at least 45 FPS and 95th percentile is at least 30 FPS with no single blocking task exceeding 150ms. - Peak memory usage attributable to the component remains below 150MB when rendering the maximum dataset. - Given gaps exceeding 24 hours between consecutive points in a series, Then the line breaks (no straight-line interpolation), a subtle gap indicator renders, and tooltips indicate Data gap where applicable. - Given an empty dataset or no data in the selected range, Then a No data available state renders without crashes, and controls remain operable. - Large datasets do not freeze the UI; interactions remain responsive and the browser main thread is not blocked for more than 150ms at a time.
Embedding, theming, SSR, and export of visible area
- Given the component is embedded in a host dashboard, When a theme object or CSS variables are provided, Then colors, fonts, and spacing update accordingly; dark mode is supported via theme prop and system prefers-color-scheme. - When the theme changes at runtime, Then the chart updates without a full remount and maintains contrast compliance. - Given server-side rendering, Then the component renders without accessing window or document, and client hydration completes without mismatches or warnings. - When the user clicks Export and selects PNG, Then a file downloads capturing the currently visible area (respecting zoom, pan, toggles, target bands, annotations, and theme) at 2x pixel ratio; when SVG is selected, Then a vector-accurate file downloads. - Exported files include legends, axis labels, target bands, and event badges; filenames include metric(s) and date range; export completes within 2 seconds on the maximum dataset. - If export fails, Then a non-blocking error message appears with a Retry option; the chart remains interactive.
Period Comparison and Delta Export
"As a VP of Engineering, I want to compare periods and export deltas so that I can include defensible metrics in quarterly reviews."
Description

Enable selection of two comparable periods (e.g., last quarter vs. prior quarter), compute averages, medians, and percent deltas for each metric and cohort, and generate a shareable report. Include an annotated summary of events between periods, statistical significance hints, and outlier callouts. Provide export options: CSV of aggregates and deltas, and a PDF summary with charts and annotations. Offer an API endpoint for scheduled automation. Normalize time zones and ensure consistent rounding for auditability.

Acceptance Criteria
Comparable Period Selection & Timezone Normalization
- Given the user selects Period A and Period B date ranges, When the durations differ by at least one day or the ranges overlap, Then the UI blocks report generation and displays a specific validation message. - Given the user selects two non-overlapping ranges of equal duration measured in whole days, When the user clicks Generate Report, Then the request is accepted and processing begins. - Given a workspace default timezone is configured, When rendering date pickers and computing day boundaries, Then all boundaries align to local midnight in that timezone, including across DST transitions without duplicate or missing days. - Given inputs where start_date > end_date or dates fall outside the data retention window, When the user submits, Then validation errors are shown inline and no processing occurs.
Aggregates, Medians, and Percent Delta Computation per Metric & Cohort
- Given valid Period A and Period B, When computing metrics (fairness_score, after_hours_rate) per cohort, Then the system returns count (n), mean, and median for each metric in both periods and for all cohorts. - Given the aggregates, When computing deltas, Then absolute_delta = periodB_mean − periodA_mean and percent_delta = ((periodB_mean − periodA_mean) / abs(periodA_mean)) × 100, with percent_delta reported as "N/A" when periodA_mean = 0. - Given rounding rules, When presenting metrics in UI, CSV, PDF, and API, Then means/medians round to 2 decimals, percentages to 1 decimal, and times to the nearest minute, using half-up rounding consistently across all outputs. - Given a cohort has no observations in one period, When generating output, Then that cohort is labeled "No data" for that period, excluded from significance testing, and included in CSV/API with nulls for missing fields. - Given classification of after-hours meetings, When computing after_hours_rate, Then meeting timestamps are converted to each participant’s local time for classification, aggregated to cohort level, and reported with date labels in the workspace timezone.
Event Annotation Between Periods in Report & Charts
- Given Period A and Period B are defined, When generating the report, Then events of types DST shift, rotation change, and headcount change occurring between Period A end and Period B end are listed with ISO-8601 timestamps, type, and description. - Given no qualifying events are found, When generating the report, Then the report displays "No relevant events" and charts render without empty placeholders. - Given an event falls within the plotted timeline, When rendering charts, Then an annotation marker with the event label appears at the correct timestamp and is included in the PDF export. - Given multiple events occur within 7 calendar days, When displaying the summary, Then events are grouped with a count badge and expandable details.
Statistical Significance Hints Display
- Given each cohort has n ≥ 30 observations in both periods, When comparing period means per metric, Then a two-tailed Welch t-test is applied and a "Likely significant" hint is shown if p < 0.05, else "Not significant". - Given multiple cohorts are tested, When computing hints, Then Benjamini–Hochberg correction at FDR 0.10 is applied and adjusted p-values are used for labeling. - Given any cohort–period has n < 30, When rendering hints, Then "Insufficient data" is shown and no significance label is applied. - Given a significance hint is displayed, When the user opens details, Then a tooltip shows n per period, mean difference, Cohen's d, and (adjusted) p-value.
Outlier Detection and Callouts
- Given daily metric values within each period for a cohort, When detecting outliers, Then the Tukey method flags values outside [Q1 − 1.5×IQR, Q3 + 1.5×IQR]. - Given outliers are detected, When generating the report, Then the summary lists the count per cohort and the top 3 dates with the largest deviations and includes links to those days in the chart. - Given the user exports data, When exporting CSV or PDF, Then an outlier_flag column is present per row in CSV and an Outliers section appears in the PDF summary with counts and dates.
Export (CSV/PDF), Shareable Report, and API Parity
- Given a completed comparison, When exporting CSV, Then the file includes a header row with columns: workspace_id, generated_at, timezone, periodA_start, periodA_end, periodB_start, periodB_end, cohort, metric, periodA_n, periodA_mean, periodA_median, periodB_n, periodB_mean, periodB_median, absolute_delta, percent_delta, significant_label, outlier_days_count, and values conform to the rounding rules. - Given a completed comparison, When exporting PDF, Then the file includes charts for each metric, event annotations, a one-page executive summary of key deltas, and a footer with generated_at (ISO-8601), workspace_id, and app version; file size is ≤ 10 MB for datasets with ≤ 50 cohorts. - Given the user generates a shareable link, When the link is created, Then it contains a signed token expiring in 30 days by default (configurable 1–90 days) and provides view-only access without login. - Given an API client requests GET /v1/period-comparison with valid auth and query params (periodA_start, periodA_end, periodB_start, periodB_end, optional cohort_filter), When processed, Then the response is 200 within 5 seconds for datasets ≤ 100 cohorts and returns JSON fields matching the CSV schema with consistent rounding and stable ordering. - Given a schedule is configured via POST /v1/report-schedules with a callback_url, When the schedule triggers, Then the service delivers CSV and PDF to the callback_url and retries up to 3 times with exponential backoff on 5xx failures.
Target Setting and Threshold Alerts
"As a team manager, I want to set targets and see when we’re off track so that I can intervene before problems grow."
Description

Allow managers to define targets per metric and team (e.g., after-hours rate below 10%, fairness score above 0.85) with effective dates and optional grace periods. Visualize target bands on charts and compute on-track/off-track status. Send optional weekly email/Slack alerts when thresholds are breached or improved beyond a material delta. Version and audit all target changes, reuse existing notification channels, and respect user notification preferences.

Acceptance Criteria
Create Target with Effective Date and Grace Period
Given a manager with Manage Targets permission selects a team T and metric M (after_hours_rate or fairness_score) When they create a target with threshold X, comparator (≤ for after_hours_rate, ≥ for fairness_score), effectiveDate E, optional gracePeriod G (0–90 days), and optional materialDelta D (≥ 0) Then the system validates ranges (after_hours_rate threshold 0–100%, fairness_score threshold 0.0–1.0; G integer days; D numeric) and blocks invalid inputs with specific error messages Then the target is persisted with a unique id and status "Scheduled" if E > now, else "Active" if E ≤ now Then the target becomes effective at E 00:00 in the team’s primary timezone and does not modify historical data Then the grace period G is stored and will suppress Off Track status and alerts until E+G days.
Versioning and Audit of Target Changes
Given an existing target version V1 for team T and metric M When a manager edits threshold, comparator, effectiveDate, gracePeriod, or materialDelta and saves Then a new immutable version V2 is created with a higher version number and effectiveDate ≥ now Then V1 remains unchanged and is marked "Superseded" starting at V2.effectiveDate Then an audit log entry is recorded with actor id, timestamp, and before/after values for all changed fields Then the audit history for the target shows V1 and V2 in chronological order.
Visualize Target Bands on Score Timeline
Given the Score Timeline is open for team T and metric M with at least one target version When the date range includes a target effective period Then the chart renders a target band/line at the threshold from its effectiveDate forward until superseded Then if multiple versions exist, band segments align to each version’s effective dates without overlap or gaps Then each band/line is labeled with comparator and threshold (e.g., "≤ 10%", "≥ 0.85") and tooltips show effective and superseded dates.
Compute On-Track/Off-Track Status per Metric
Given a data point value V for metric M at time t within a target’s effective period When M = after_hours_rate (comparator ≤) Then status is "On Track" if V ≤ threshold, else "Off Track" When M = fairness_score (comparator ≥) Then status is "On Track" if V ≥ threshold, else "Off Track" Then during the grace period (t ∈ [effectiveDate, effectiveDate+G)) status is "In Grace" and excluded from Off Track counts Then if no target is active at t, status is "N/A"; if V is missing, status is "Unknown"; neither condition triggers alerts.
Weekly Alerts for Breach and Improvement with Material Delta
Given the weekly alert job runs after the prior week closes When a team/metric has Off Track status for that week and is not within a grace period Then send exactly one threshold breach alert via existing email and/or Slack channels to configured recipients including team, metric, current value, target, deviation, and a link to the Score Timeline Then do not send more than one breach alert per team/metric per week Then when the current week’s metric improves by at least the configured materialDelta D versus the previous completed week and either crosses to On Track or reduces deviation by ≥ D, send one improvement alert; otherwise suppress improvement alerts.
Respect Notification Preferences and Reuse Existing Channels
Given users have per-team, per-channel notification preferences for threshold alerts When alerts are generated Then alerts are sent only to users with the threshold-alert preference enabled for that team and channel Then existing email sender identity and Slack app/bot are reused; no new channels are created Then if a user has muted or unsubscribed from threshold alerts on a channel, no alert is delivered to that channel for that user.
Multi-Team and Multi-Metric Scoping and Isolation
Given targets exist for multiple teams (T1, T2) and metrics (after_hours_rate, fairness_score) When computing status, rendering target bands, and sending alerts Then each team/metric pair uses only its own active target versions Then actions on T1/M1 do not affect T2/M2 results or alerts Then users without access to T2 cannot view or receive alerts related to T2’s targets.
Data Permissions and Privacy Safeguards
"As an org admin, I want data permissions and privacy controls so that insights are shared appropriately without exposing individuals."
Description

Enforce role-based access control for timelines, metrics, and annotations at org/department/team levels. Apply privacy thresholds to suppress or bucket data when cohort size falls below a configurable minimum to prevent individual identification. Ensure all exports and API responses inherit the same scoping and redaction rules. Provide an admin UI to manage roles, scopes, and minimum cohort thresholds, and log access to sensitive dashboards.

Acceptance Criteria
RBAC scope enforcement for Score Timeline views
Given a user with Org Admin role and org-wide scope, When they open any Score Timeline (org/department/team), Then all authorized data and annotations are visible. Given a user with Department Lead role scoped to Department A, When they open the Score Timeline for Department A, Then data and annotations for Department A are visible; When they attempt to open Department B or any team outside Department A, Then access is denied with 403 and no data is returned. Given a user with Team Lead role scoped to Team X, When they open the Score Timeline for Team X, Then Team X data is visible; When they open Department-level timelines, Then only aggregates derived exclusively from authorized teams are shown, subject to privacy thresholds; otherwise the view is redacted. Given a user without a permitted role, When they navigate to Score Timeline, Then they see an access denied state and no metrics or annotations are loaded. Given any unauthorized API request for out-of-scope resources, When evaluated by the service, Then the response is 403 and contains no partial data.
Privacy threshold suppression and bucketing on small cohorts
Given the minimum cohort threshold is configured to 5, When a time-bucket contains fewer than 5 unique participants in the selected scope, Then fairness and after-hours values for that bucket are masked ("—"), and tooltips state "Insufficient cohort size". Given all time-buckets in a view are below threshold, When rendering the Score Timeline, Then the chart area is replaced by a message indicating insufficient cohort size and no point-level values are shown. Given the admin has enabled bucket-to-parent roll-up, When a bucket is below threshold at Team level, Then the value is replaced by the corresponding Department-level aggregate if that aggregate meets the threshold; otherwise it remains masked. Given a user attempts to infer values by narrowing filters, When the resulting cohort falls below threshold, Then the UI and API both mask values and do not leak raw counts that could re-identify individuals. Given thresholds are changed, When the page is refreshed, Then suppression/roll-up behavior reflects the new threshold within 5 minutes.
Admin UI for managing roles, scopes, and cohort thresholds
Given an Org Admin navigates to Admin > Permissions & Privacy, When they create or edit a role, Then they can assign scopes at org/department/team levels and grant permissions (view timelines, export, view sensitive annotations). Given an Org Admin updates the org-wide minimum cohort threshold to a value between 1 and 50, When they save, Then the value is validated, persisted, and an audit entry capturing before/after values, actor, and timestamp is recorded. Given department/team overrides are configured, When saved, Then effective thresholds are displayed and used by the system in precedence order: team override > department override > org default. Given any change is saved, When up to 5 minutes have elapsed, Then new roles/scopes/thresholds take effect across UI, exports, and API; a banner shows the last applied time. Given an invalid input (non-integer, out of range), When submitted, Then the form blocks save and displays a clear error message.
Scoping and redaction parity for exports and API responses
Given a user with Department A scope and threshold=5, When they export CSV/PDF of the Score Timeline for Department A, Then only Department A data is included and any sub-threshold buckets are masked exactly as in the UI. Given the same user calls the public API for timelines with query parameters outside their scope, When the request is processed, Then the service returns 403 with no data payload and writes an audit log entry. Given an export is generated, When opened, Then the file contains a header indicating applied scope(s), time range, and threshold used; masked cells are represented as null or "—" consistently across formats. Given a bucket is masked in the UI due to privacy threshold, When exporting or retrieving via API, Then the corresponding value is also masked and no alternative fields (e.g., raw counts) reveal the suppressed value. Given a user without export permission, When they attempt to export, Then the export action is disabled in UI and returns 403 via API.
Sensitive event annotations visibility control
Given annotation types are classified as sensitive (headcount moves, rotation changes) and non-sensitive (DST shifts, target changes), When a Team Lead without "View Sensitive Annotations" permission views a timeline, Then non-sensitive annotations within their scope are visible and sensitive annotations are hidden or shown as redacted labels without details. Given a Department Lead with the permission and scope to Department A, When viewing the timeline, Then sensitive annotation details (who/what where applicable) are visible only for Department A; sensitive annotations outside Department A remain hidden. Given an Org Admin toggles the permission on a role, When refreshed, Then annotation visibility updates within 5 minutes and changes are captured in the audit log. Given a user attempts to access a sensitive annotation detail via deep link outside their scope, When requested, Then the service returns 403 and reveals no content.
Audit logging for Score Timeline access and exports
Given any access to Score Timeline UI, exports, or API, When the action occurs, Then an immutable audit log entry is recorded with timestamp (UTC), user ID, role, scopes, resource, action (view/export/api), filters/time range, result (success/denied), redaction count, and source IP. Given an Org Admin opens Admin > Audit Logs, When filtering by user, scope, action, or time range, Then matching entries are returned within 2 seconds for up to 10,000 records and can be exported as CSV. Given audit logs are retained for 12 months, When viewing logs older than 12 months, Then they are not available to users and a retention notice is displayed. Given the logging subsystem fails, When a user attempts to access sensitive dashboards or export, Then the request is blocked, the user sees a descriptive error, and an alert is emitted to the monitoring channel. Given a user attempts to modify or delete an audit entry, When the request is made, Then it is rejected and logged as a denied attempt.
Data Freshness and Provenance Indicators
"As a stakeholder, I want to see data freshness and provenance so that I trust the numbers and can reproduce reports."
Description

Surface last-updated timestamps per metric and cohort, source data status, and calculation version directly in the timeline UI. Provide backfill progress indicators and a provenance panel listing upstream changes affecting comparability (e.g., timezone database updates, rotation algorithm version). Allow users to pin a computation version for exports to ensure reproducibility of quarterly reports.

Acceptance Criteria
Per-Metric and Per-Cohort Last-Updated Timestamps
- Given I view the Score Timeline with selected metrics and cohorts, when the chart loads, then each metric–cohort series displays a last-updated timestamp in the user's locale with timezone offset. - Given the backend updates a series’ data, when I refresh or the auto-refresh cycle elapses (≤60s), then the last-updated timestamp reflects the latest ingestion time. - Given freshness SLAs are configured (defaults: fairness_score=24h, after_hours_rate=24h), when a series exceeds its SLA, then a Stale badge is shown next to that series and a banner summarizes affected series count. - Given I hover any data point, when the tooltip opens, then it shows the point timestamp and the series’ last-updated timestamp.
Source Data Health and Calculation Version Badging
- Given the data pipeline publishes health and lag metrics, when the timeline renders, then each series shows a source status badge (Healthy/Delayed/Degraded) with a tooltip indicating current lag vs SLA. - Given the computation version is available from the service, when the timeline renders, then each series shows a calculation_version (e.g., fairness_v2.3.1) in the legend/tooltip. - Given a new calculation version becomes active, when I reload the view, then the updated version is displayed and a version-change annotation appears at the activation timestamp. - Given multiple cohorts are displayed in a combined legend entry, when their calculation versions differ, then the UI indicates Mixed and the per-cohort breakdown appears in the tooltip.
Backfill Progress Visualization and Export Guardrails
- Given a backfill job is running for any visible series, when I open the timeline, then a progress indicator shows percent complete, last backfilled date, and ETA, updating at least every 30 seconds. - Given a backfill is in progress for a date range, when the chart renders, then the in-progress range is visually de-emphasized (e.g., shaded) and labeled Backfill In Progress. - Given the selected export range intersects an in-progress backfill, when I click Export, then I am prevented from exporting unless I choose Exclude in-progress periods, and the dialog shows the number of rows that will be excluded. - Given a backfill completes, when I refresh the timeline, then stale badges clear and the progress indicator is removed within ≤60 seconds.
Provenance Panel and Comparability Annotations
- Given I click the Provenance button, when the panel opens, then it lists events with timestamp, type, affected metrics/cohorts, before→after versions (if applicable), description, and a link to documentation. - Given provenance events such as timezone DB updates, rotation algorithm changes, DST shifts, or headcount rule changes occur within the visible window, when the timeline renders, then vertical markers and tooltips appear at those timestamps. - Given Compare mode is enabled across periods that span a provenance event, when the comparison renders, then a comparability warning is displayed and the affected periods are flagged in the legend. - Given I filter events by type or time in the provenance panel, when I apply filters, then only matching markers remain visible and the panel count updates.
Pin-and-Export Computation Version for Reproducibility
- Given I open Export settings, when I select a computation version to pin, then the pinned version is saved with the view and displayed in the export dialog and timeline header. - Given a pinned version is set, when I export the same range and filters at two different times, then the metric values and deltas in both exports are identical and the metadata records the pinned version. - Given the pinned version is deprecated or unavailable, when I attempt to export, then the export is blocked with a clear message and an option to select an available version; no partial export occurs. - Given no pinned version is selected, when I export, then the latest stable version is used and recorded in the export metadata.
Exported Files Include Freshness and Provenance Metadata
- Given I export any timeline data (CSV or JSON), when the file is generated, then it includes a metadata section with export_time_utc, schema_version, metric_ids, cohort_ids, last_updated_per_series (ISO 8601 UTC), calculation_version per series, source_status per series, backfill_status per series, provenance_events (ids, types, timestamps), and pinned_version (if any). - Given provenance events affect the exported range, when I inspect the export, then affected rows or periods are flagged with a comparability field set to true and a reference to the event id(s). - Given required metadata cannot be assembled, when I attempt to export, then the export fails with an actionable error indicating missing fields rather than emitting a partial file.

Region Benchmarks

Benchmark each region against internal peers and industry percentiles with target bands and variance flags. Identify outliers quickly, set realistic goals, and export benchmark gaps to BI. Context turns raw numbers into actionable priorities for wellbeing and policy teams.

Requirements

Benchmark Metric Normalization Layer
"As a workforce analytics lead, I want normalized, comparable metrics across regions so that benchmark comparisons reflect true differences rather than timezone or data inconsistencies."
Description

Provide a transformation layer that standardizes scheduling and meeting metrics across regions by accounting for local timezones, core work windows, regional holidays, and meeting types. Define canonical KPIs (e.g., after-hours meeting rate, meeting load per FTE, fairness rotation balance) and harmonize calculations to ensure apples-to-apples comparisons. Ingest data from TimeTether’s scheduling events and connected calendars, handle late-arriving data, and apply data quality checks with versioned metric definitions. Deliver freshness SLOs and lineage so stakeholders can trust cross-region comparability.

Acceptance Criteria
Normalize After-Hours Meeting Rate by Local Work Windows
Given regional timezone mappings and core work windows configured per region and role And meeting event logs with start/end times and participant IDs When the normalization job computes after-hours meeting rate for a region over the trailing 30 calendar days Then after-hours minutes are counted per participant as minutes of attended meetings outside that participant’s local work window on their local calendar days And total meeting minutes are minutes of attended meetings within the window, excluding canceled/no-show events And the region rate equals sum(after-hours minutes) / sum(total meeting minutes) with precision to 0.1% And meetings spanning midnight are split by participant local day before classification And recomputing in any timezone yields identical rates for the same inputs
Apply Regional Holidays and Exceptions
Given an authoritative regional holiday calendar and company exception days mapped to participants When computing meeting_load_per_FTE and after_hours_meeting_rate Then minutes on a participant’s local regional holiday are excluded from both numerator and denominator of those KPIs And those minutes are written to holiday_meeting_minutes for audit And exception days override holiday status where overlap occurs And >= 99% of active participants are mapped to a holiday calendar; otherwise the region-day is flagged DQ_FAIL_HOLIDAY_COVERAGE
Late-Arriving Data Backfill and Idempotency
Given calendar and scheduling events may arrive up to 7 days late or out-of-order When late or corrected events are received within the window Then affected aggregates are re-calculated only for impacted region-days and trailing windows And duplicate or superseded events do not change final aggregates (idempotent upserts) And lineage for impacted days records backfill batch_id, event counts before/after, and source timestamps And per region-day completeness >= 99% by T+1 09:00 local time; else mark STALE
Canonical KPI Definitions Consistency
Given a frozen golden dataset and metric definition version v1.0.0 When computing canonical KPIs Then after_hours_meeting_rate = sum(participant after-hours minutes) / sum(participant total meeting minutes) over trailing 30 days per region And meeting_load_per_FTE = sum(participant total meeting minutes) / distinct_active_FTEs over trailing 30 workdays per region And fairness_rotation_balance = coefficient_of_variation of participant after-hours minutes over trailing 30 days per region And results are reproducible within 0.1% across environments using v1.0.0 And any formula change requires a semver bump with changelog
Versioned Metric Definitions, Lineage, and Reproducibility
Given versioned metric artifacts (SQL/code/config) When a metric run completes Then each output row includes metric_name, version, run_id, input_snapshot_id, source_system_ids, and transform_hash And lineage is queryable to show sources, steps, and timestamps for any region-day And selecting a prior version reproduces prior outputs for the same inputs And deployments without version increment on definition change are blocked
Data Quality Checks and Variance Flags
Given DQ thresholds: timezone coverage >= 99.5%, work-window coverage >= 99.0%, required-field nulls < 0.5% When a region-day violates thresholds Then exclude that region-day from benchmarks and tag with specific DQ failure codes And emit alert with metric, region, threshold breached, and counts within 5 minutes And compute variance flags per metric per region when value is outside configured target band (INFO/WARN/CRIT) with gap_to_band calculation
Freshness SLOs and Monitoring
Given daily pipelines scheduled per region When a business day run executes Then 95% of regions have normalized metrics published by 08:00 local time and 99.9% by 10:00 local time And freshness, last_success_at, and next_expected_at are recorded per region-day And SLO breaches trigger alerts within 15 minutes and mark region-day STALE until recovered And backfills do not regress SLO metrics for unaffected regions
Region & Peer Group Mapping
"As a policy manager, I want flexible region and peer group definitions so that benchmarks align with how our organization and markets are structured."
Description

Maintain a flexible mapping of users and teams to regions, timezones, and peer groups with support for custom regional groupings and rollup hierarchies. Synchronize with HRIS/org directory, handle multi-region contributors, and preserve change history for backfills. Ensure mappings drive both internal peer benchmarks and industry comparisons, enabling accurate segmentation and aggregation in dashboards and exports.

Acceptance Criteria
HRIS Sync: Effective-Dated Region/Timezone/Peer Mapping
Given a user exists in the HRIS with region, timezone, and peer_group fields and an effective_start date, When the scheduled sync runs after a change, Then a corresponding mapping record is created or updated within 10 minutes with fields matching HRIS exactly and effective_start set as provided. Given HRIS updates a user’s region/timezone/peer_group with a new effective_start S2 greater than the prior start, When the sync runs, Then the previous active mapping is end-dated to S2 minus 1 second and a new active mapping begins at S2. Given multiple HRIS changes occur for the same user on the same day, When the sync runs, Then the update with the latest modified_at timestamp wins and earlier changes are superseded without duplicates. Given a user is terminated or removed from HRIS, When the sync runs, Then the active mapping is end-dated at the termination date (23:59:59 local to the user’s last timezone). Given a sync is retried after a failure, When it reprocesses the same HRIS changes, Then the operation is idempotent and does not create duplicate mappings for the same effective range.
Custom Regional Groups and Rollup Hierarchy Integrity
Given an admin creates a custom region group with valid member region codes, When saved, Then the group is available for segmentation and benchmarks within 2 minutes and is returned by the groups API with a stable ID. Given an admin defines a rollup hierarchy with a parent and multiple children, When validated, Then cycles are rejected, each child has at most one parent, and all region codes resolve to known regions. Given a custom group is renamed, When dashboards refresh, Then all references display the new name without changing the group’s ID within the next refresh cycle (<5 minutes). Given a child region is reassigned from one parent to another, When the change is saved, Then rollup aggregates reflect the new hierarchy in the next refresh (<5 minutes) and historical reports prior to the change remain unchanged (effective-dated).
Multi-Region Contributor Weighting and Aggregation
Given a user is assigned to N regions, When weights are provided, Then the system requires weights to sum to 100% and blocks save with an error if they do not. Given a user is assigned to multiple regions without explicit weights, When saved, Then equal weights summing to 100% are auto-assigned and displayed. Given weighted multi-region mappings exist, When regional metrics and benchmarks are calculated, Then the user’s contributions are allocated to each region proportional to the stored weights with no rounding error greater than 0.1% at the region total level. Given after-hours calculations depend on timezone, When meetings are attributed to a region, Then after-hours flags are computed using that region’s timezone window for the user’s allocated share of those meetings.
Change History Preservation and Backfill Recalculation
Given effective-dated mappings are stored, When querying the mapping for a user on date D via API, Then the response returns exactly the mapping version whose effective range covers D. Given an admin edits a past mapping’s effective range, When the change is saved, Then overlapping ranges for the same user-dimension are rejected and the edit is blocked with a clear error. Given a valid change to historical mappings is saved, When the backfill job runs, Then affected benchmark periods are reprocessed using the effective mappings for those periods and an audit record is written linking job ID to impacted date ranges within 24 hours. Given compliance requires auditability, When requesting the change log for a user, Then the system returns an immutable history of mapping changes (who, what, when, before/after, source) retained for at least 7 years.
Peer Group and Industry Cohort Mapping for Benchmarks
Given internal peer groups are defined (e.g., role, level, function), When benchmarks run, Then cohort membership includes only users mapped to that peer_group_id for the effective date range. Given industry cohort tags are provided by an external taxonomy, When industry comparisons run, Then each mapped user resolves to exactly one valid industry cohort; unknown or missing tags are excluded and flagged. Given a user lacks a valid industry cohort mapping, When rendering industry percentile metrics, Then the metric is suppressed for that user/segment and a data quality warning is surfaced to admins. Given peer and industry mappings are updated, When the next benchmark computation occurs, Then deltas in cohort counts are logged and visible in the run summary.
Dashboard Segmentation and BI Export Fidelity
Given a dashboard is filtered to a specific region/group/peer cohort and date range, When compared to a raw export for the same filters, Then entity counts match exactly and metric differences (due to rounding) are ≤ 0.5% absolute. Given the BI export job runs on schedule, When a file is delivered, Then it contains required fields [user_id, team_id, region_code, custom_region_group_ids, peer_group_id, timezone, effective_start, effective_end, weight] with correct data types and passes schema validation. Given the export covers a date range R, When loaded into BI, Then the number of active mapping rows equals the count of effective-dated mappings overlapping R and there are no NULLs in required fields. Given a mapping or grouping is changed, When the next export is generated, Then the change is reflected if and only if its effective dates overlap the export window.
Data Validation, Error Handling, and Alerts
Given an inbound HRIS record contains an invalid region code or unknown peer_group_id, When the sync runs, Then the record is quarantined with a validation error, no partial writes occur, and an alert is sent to the designated channel within 5 minutes. Given overlapping effective ranges are submitted for the same user and dimension, When attempting to save, Then the request is rejected with a 400 error detailing the conflicting ranges. Given a proposed custom region hierarchy introduces a cycle, When validating, Then the save is blocked and the cycle path is returned to the admin. Given a sync run results in >0.5% of records quarantined, When the run completes, Then a P1 alert is raised and publish of new mappings to downstream benchmarks is blocked until errors are under threshold or explicitly overridden by an admin with audit logging.
Percentile Benchmark Engine
"As a wellbeing analyst, I want percentile scores for each region so that I can see where we over- or under-perform against peers."
Description

Compute internal peer and industry percentile statistics per metric and region using robust methods (rolling windows, sample-size thresholds, winsorization) to reduce volatility. Support selectable baselines (company, business unit, industry), caching for interactive use, and a governed ingestion of industry datasets. Provide scheduled recalculation, versioned outputs, and guardrails to suppress unreliable percentiles when data is sparse.

Acceptance Criteria
Robust percentile computation per region and metric
Given a dataset of weekly metric values for multiple regions over 12 weeks and configuration rolling_window_weeks=8 and winsorize_limits=(0.05,0.95) When the engine computes 10th, 25th, 50th, 75th, and 90th percentiles by region and metric using the selected baseline Then output percentiles per region/metric equal the percentiles computed by the approved reference implementation within an absolute tolerance of 0.5 units or 1% of metric range, whichever is smaller And winsorization is applied before percentile calculation (values below 5th set to 5th; above 95th set to 95th) And the rolling window includes only rows within the last 8 complete weeks relative to as_of_date And ties are handled using the nearest-rank method with linear interpolation disabled unless configured otherwise And output includes metadata: baseline_id, as_of_date, window_weeks, winsorize_limits, percentile_method="nearest_rank"
Sparse data suppression guardrails
Given min_sample_size=30 and a region/metric window with sample_size=24 When computing percentiles Then no percentile values are emitted for that region/metric/window And the record includes status="suppressed", reason_code="insufficient_sample", and sample_size=24 Given sample_size=30 When computing percentiles Then percentile values are emitted with status="ok" And suppression thresholds are configurable per metric and effective on the next calculation run
Selectable baselines (company, business unit, industry)
Given baseline="company" for metric M, region R, as_of_date D When running computation Then the cohort used comprises all company entities matching M constraints within R and the configured time window Given baseline="business_unit" with bu_id=BU123 When running computation Then the cohort filters to BU123 and outputs are labeled baseline_id="BU:BU123" Given baseline="industry" with industry_dataset_version=V (approved) When running computation Then the cohort uses industry dataset V and outputs are labeled baseline_id="industry:V" And if V is not approved or is deprecated, the computation is blocked with reason_code="baseline_unavailable"
Caching for interactive queries with invalidation
Given cache_ttl=300s and a user executes the same percentile query twice within 5 minutes When the second query runs Then it is served from cache with response_time<=200ms and cache_hit=true Given a new input dataset version V+1 is published When the same query is executed after publication Then cache is invalidated and the response contains version_id=V+1 and cache_hit=false And cache keys include baseline, region, metric, as_of_date, window_weeks, winsorize_limits, dataset_version, and access_scope to prevent data leakage
Scheduled recalculation and versioned outputs
Given a scheduler configured with cron "0 2 * * *" UTC When the job runs at the scheduled time Then a new immutable version is created only if any input dataset version or configuration changed since the last run And version_id increments monotonically and includes timestamp and code_commit_id And each output record includes version_id, effective_start, effective_end, input_lineage, and baseline_id Given a job failure occurs during computation or publish When the run completes Then no partial version is published and an on-call alert is sent within 2 minutes of failure detection
Governed industry dataset ingestion and validation
Given an industry dataset file provided by a user with role="Data Steward" When ingestion starts Then the system validates schema against the registered contract, verifies checksum, and runs defined quality checks (null thresholds, range bounds, duplicates) And if any critical check fails, the dataset is rejected with status="rejected" and a machine-readable error report (fields: check_id, severity, message) And if all checks pass, the dataset is stored with metadata: industry_dataset_version=V, approval_status="approved", effective_date, source_uri, steward_id And only datasets with approval_status="approved" are eligible for baseline="industry" computations; pending or rejected datasets are not queryable And deprecating a dataset sets approval_status="deprecated" and excludes it from new computations while preserving historical referential integrity
Target Bands & Variance Flags
"As a regional lead, I want clear target bands and automatic variance flags so that I can prioritize interventions where they are most needed."
Description

Enable admins to configure target bands per metric and region (e.g., green/amber/red thresholds) and automatically compute variance and severity levels. Surface flags in UI components and APIs, trigger optional alerts/webhooks, and audit all threshold changes. Support scenario planning by previewing how proposed targets would flag current data to help set realistic, outcome-oriented goals.

Acceptance Criteria
Admin configures per-region target bands for a metric
Given an Org Admin with Benchmarks:Manage permission and an existing metric-region pair When the admin defines Green/Amber/Red bands with non-overlapping numeric thresholds and a directionality (lower_is_better or higher_is_better) and saves Then the system validates monotonic thresholds and unit compatibility and returns 400 with field-level errors for any invalid input And on success, the configuration is stored with config_version, effective_at, created_by, and reason And recalculation of flags for the affected region/metric completes within 60 seconds of save And only users with Benchmarks:Manage can create/update/delete target bands; others receive 403
Automatic variance and severity computation
Given an active target-band configuration and an actual metric value When the system evaluates the metric for a region Then severity is assigned to one of [green, amber, red] according to thresholds and directionality And variance_value is computed in native units relative to the nearest green boundary (0 when inside green; positive means worse per directionality) And severity_level is exposed as 0=green, 1=amber, 2=red And exact threshold boundary values resolve to the less severe band And outputs include severity, severity_level, variance_value, variance_direction, evaluated_at, and config_version
UI surfaces flags and context on Region Benchmarks
Given a user views the Region Benchmarks dashboard or metric cards When region/metric data is rendered Then each card shows a color-coded status chip (WCAG AA contrast), textual severity label, and variance_value with unit And a tooltip/details panel shows thresholds, directionality, last changed by, effective_at, and config_version And users can sort and filter by severity and variance_value And preview (scenario-planning) values are clearly labeled as Preview and do not replace Active values
API exposes severity, variance, and thresholds
Given an authorized API client When it requests GET /benchmarks/flags with region_id and/or metric_id filters Then the API returns 200 with JSON containing region_id, metric_id, severity, severity_level, variance_value, unit, directionality, thresholds, config_version, effective_at, evaluated_at And only data for authorized regions is returned; unauthorized access yields 403 And the endpoint supports ETag with If-None-Match and returns 304 when unchanged And p95 latency is ≤ 500 ms for up to 100 region-metric pairs
Alerts and webhooks on threshold crossings
Given webhooks are enabled for a metric/region and a destination URL is configured When severity changes (e.g., amber→red or red→amber/green) or variance_value crosses an admin-defined delta Then a signed POST is delivered within 60 seconds containing event_id, event_type, timestamp, region_id, metric_id, previous_severity, new_severity, variance_value, config_version And deliveries are retried with exponential backoff up to 10 times on 5xx/timeout; never retried on 2xx And events are idempotent and deduplicated by event_id for 24 hours And per-destination rate limits of 60 events/minute are enforced And no alerts/webhooks are emitted for preview-only simulations
Audit trail for target-band changes
Given a target-band configuration is created, updated, or deleted When the change is saved Then an audit record is appended with timestamp, actor, scope (org/region/metric), before/after thresholds, directionality, reason, config_version, actor_ip, correlation_id And audit entries are immutable and viewable only by Benchmarks:Manage or Audit:View roles And the audit log is filterable by date, actor, region, metric and exportable to CSV And audit retention is at least 24 months And API/UI surfaces include config_version to correlate with audit records
Scenario planning preview of proposed targets
Given an admin opens Scenario Planning for a metric and region(s) When the admin inputs proposed bands and clicks Preview Then the system simulates severity and variance against current data without persisting changes And shows deltas versus Active (counts by severity and impacted region-metric pairs) And allows toggling between Active and Preview and promoting Preview to Active with confirmation And no recalculations, alerts, webhooks, or API outputs change until promotion is confirmed And exiting Preview discards unpromoted changes
Outlier Explorer & Contextual Drill-down
"As a people operations manager, I want to drill into outlier regions with context so that I understand drivers and can craft targeted actions."
Description

Provide an interactive view to quickly identify outlier regions against targets and percentiles, with drill-down to contributing factors such as after-hours load, meeting duration distributions, and fairness-rotation effects. Include filters by team, level, meeting type, and time range, with trendlines, comparative baselines, and annotations to capture qualitative context and action plans.

Acceptance Criteria
Outlier Regions Overview Flags and Sorting
Given a selected KPI and admin-configured target bands and percentile thresholds And region metrics for the selected time range are available When the user opens the Outlier Explorer Overview with no filters Then each region displays the KPI current value, target band, industry percentile, absolute and percentage variance And regions exceeding the red threshold are flagged Red, those between amber thresholds are flagged Amber, others are Neutral And selecting "Sort by variance" orders regions by absolute percentage variance descending And the overview loads within 2 seconds for cached data and within 5 seconds for uncached data
Drill-down to Contributing Factors Panel
Given a region flagged as an outlier and a selected KPI When the user clicks the region row/card Then a drill-down shows after-hours load %, meeting duration distribution (histogram), fairness-rotation imbalance index, and meeting type breakdown for the same filters and time range And each sub-metric displays comparisons against organization median and selected industry percentiles (50th/75th/90th) And each sub-metric shows its contribution to the variance as a percentage of the total KPI gap And the drill-down renders within 3 seconds and matches backend aggregates for the same slice
Multi-dimensional Filtering and Persistence
Given team, level, meeting type, and time range filters are available When the user applies any combination of these filters Then the overview and drill-down recompute to include only matching meetings and participants And the selected filters persist in the URL and are retained on refresh and when the link is shared And time range supports presets (Last 7/30/90 days, Quarter-to-date) and custom start/end And after-hours classification uses each participant's local timezone And empty-result states display "No data for selected filters" with a Reset option
Trendlines with Comparative Baselines
Given a selected region and KPI When the user opens the Trend view Then a weekly trendline for the last 12 weeks renders with a shaded target band and lines for organization baseline and selected industry percentile And hover tooltips show week start date, region value, target bounds, baseline values, and variance And toggling baselines on/off updates the chart within 300 ms And data points align to calendar weeks and reflect all active filters
Fairness-Rotation Effect Attribution
Given fairness-rotation settings are enabled for the organization When the user views a region’s drill-down Then the Fairness Impact module displays the KPI under actual rotation and a simulated no-rotation baseline And the delta is shown as both percentage and hours per person per month And toggling "Use no-rotation baseline" updates variance attribution and recalculates contributions within 1 second
Annotations and Action Plans
Given the user has edit permissions When the user adds an annotation to a date or week for a region and KPI Then Title and Category fields are required; Owner and Due Date are optional And the annotation appears on the chart and in the region timeline with author and timestamp And the user can edit or delete their own annotations; changes are versioned with history And annotations are scoped to active filters and included in data exports
Export Benchmark Gaps to BI
Given current filters and a selected KPI When the user clicks Export Then a dataset with region ID/name, KPI current value, target band bounds, variance (absolute and %), percentile rank, and open action count is generated And the user can choose CSV or JSON; the file downloads within 10 seconds for up to 10,000 rows And the export reflects all filters and the selected time range and includes a CSV data dictionary tab
BI Export & Data Contracts
"As a data engineer, I want scheduled, schema-stable exports of benchmark gaps so that I can integrate them into our BI dashboards without breakage."
Description

Deliver scheduled exports of benchmark gaps, percentile ranks, targets, and flags to enterprise BI via secure S3/CSV drops, Snowflake/BigQuery connectors, and a REST API. Provide stable, versioned schemas with a data dictionary, dataset lineage, and backward-compatible changes. Include job monitoring, retries, and access controls to ensure reliable downstream reporting and analytics.

Acceptance Criteria
Scheduled S3/CSV Export Delivery
Given daily exports are enabled for Region Benchmarks When the scheduler triggers at 04:00 UTC Then CSV files are written to s3://timetether-bi/{env}/region_benchmarks/as_of_date=YYYY-MM-DD/ with server-side encryption (SSE-KMS) and public access blocked And each file follows the naming pattern region_benchmarks_v{MAJOR.MINOR}.csv.gz and is gzip-compressed And the CSV header exactly matches the v{MAJOR.MINOR} data contract with columns: export_run_id, as_of_date, region_id, metric_key, benchmark_gap, percentile_rank, target_band_min, target_band_max, variance_flag, calculation_version, schema_version And the job completes successfully within 15 minutes of trigger with a 7-day success rate >= 99.5% And per-region row counts match the authoritative warehouse table within 0.1% variance And rerunning the same export_run_id is idempotent (no duplicate files or appended rows) And PII columns are excluded by contract
Snowflake and BigQuery Connectors Availability
Given Snowflake data share and BigQuery authorized dataset connectors are configured When the daily export completes Then new partitions (as_of_date=YYYY-MM-DD) are queryable in Snowflake and BigQuery within 10 minutes And schemas and data types match the v{MAJOR.MINOR} data contract; no breaking changes without MAJOR version And row counts in connectors equal the S3 export row counts And access is restricted to whitelisted roles/service accounts; unauthorized access returns 403 And connections use TLS 1.2+ with mutual auth where supported And metadata columns include schema_version and calculation_version
REST API Export Endpoints
Given an API client with valid OAuth2 client credentials with scope benchmarks.read When it calls GET /api/v1/region-benchmarks?as_of_date=YYYY-MM-DD&region_id=...&metric_key=... with page_size=1000 Then the response is 200 within P95 latency <= 500 ms for 1000 rows and includes data[], next_cursor, and schema_version And the endpoint supports If-None-Match ETag and returns 304 when unchanged And rate limiting is enforced at 600 requests/min per client with 429 responses including a Retry-After header when exceeded And pagination is cursor-based, stable for 24 hours, with no duplicates or omissions across pages And error responses include trace_id and machine-readable error codes
Versioned Data Contracts and Change Management
Rule: Contracts use semantic versioning MAJOR.MINOR.PATCH; all artifacts carry schema_version Rule: Backward-compatible changes (e.g., adding nullable columns) are allowed only in MINOR; PATCH for non-structural fixes; no breaking change without MAJOR Rule: Any MAJOR change requires >= 60 days deprecation notice and dual-publishing of old and new versions for >= 30 days Rule: A data dictionary with field definitions, data types, units, allowed values, and examples is published in the Developer Portal with last_updated timestamp Rule: A machine-readable schema (JSON Schema or Avro) is published; builds validate payloads against schema and fail on mismatch Rule: Dataset lineage is documented per version, linking source tables and transformations with export_run_id; lineage is retrievable via a metadata file or API Rule: A CHANGELOG is maintained; clients can select versions via Accept-Version header or version query parameter
Job Monitoring, Retries, and Alerting
Given scheduled exports run daily When a destination job fails transiently Then the system retries up to 3 times with exponential backoff starting at 2 minutes and capping at 15 minutes And a run is marked failed only after all retries are exhausted And all runs emit metrics (success, failure, duration, rows_exported, destinations_ok) to Prometheus and are visualized on a dashboard with SLO success rate >= 99.5% (7-day rolling) And on any failed run, alerts are sent within 5 minutes to on-call via PagerDuty and Slack with export_run_id and error summary And logs are structured, correlated via trace_id across components, and retained for 90 days
Access Control and Security
Rule: S3 buckets enforce least-privilege IAM; only designated cross-account roles can read; public access blocked; SSE-KMS enabled with annual key rotation Rule: Snowflake shares use reader accounts restricted to an IP allowlist; BigQuery access via service accounts with dataset-level roles Rule: REST API requires OAuth2; mTLS is supported for enterprise clients; all network traffic uses TLS 1.2+ with HSTS enabled Rule: Secrets are stored in a vault; no secrets in code or logs; credentials rotated every 90 days Rule: Audit logs capture access to exports and API endpoints with actor, timestamp, and action; admins can export access logs Rule: Exports exclude PII and respect regional data residency settings
Export Accuracy and Reconciliation
Given a known snapshot dataset for at least 2 regions and 3 metrics When exports are generated via S3, Snowflake/BigQuery connectors, and the REST API for the same as_of_date Then benchmark_gap equals (team_value - target_midpoint) in hours rounded to one decimal; percentile_rank matches warehouse within ±0.5 percentile points; variance_flag is true when |gap| exceeds target band by > 5% And timestamps are normalized to UTC ISO-8601; as_of_date uses YYYY-MM-DD And rows are unique on (as_of_date, region_id, metric_key) with no duplicates; region_id and metric_key values validate against reference enumerations And cross-channel parity holds: row counts identical across channels; MD5 per-row checksum match rate >= 99.99% between S3 and connectors; API returns identical records across full pagination And a per-run reconciliation report is produced and attached to run metadata
Role-based Access & Privacy Controls
"As a compliance officer, I want access controls and privacy safeguards on benchmark data so that we meet regulatory and internal policy requirements."
Description

Enforce RBAC for benchmark views, APIs, and exports with region-scoped permissions. Minimize PII and display only aggregate data with privacy thresholds to prevent re-identification in small populations. Log access for compliance, support data retention policies, and integrate with SSO/SCIM to keep permissions current as org structures change.

Acceptance Criteria
RBAC: Region-Scoped Benchmark Views
Given a user has role Regional Viewer with regions [EMEA, APAC] When they open the Region Benchmarks dashboard Then only EMEA and APAC appear in navigation, filters, and visualizations Given a user without access to a region When they navigate directly to that region’s URL or API endpoint Then the system returns 403 Forbidden and no restricted data is included in the response body or error message Given a cross-region aggregate widget When rendered for a region-scoped user Then the aggregation includes only the user’s permitted regions Given a drill-down from aggregate to sub-regions When any result would include regions outside the user’s scope Then those rows are omitted and totals are recalculated over permitted regions only Given an admin updates a user’s region assignments When the user refreshes the dashboard Then the new scopes take effect within 5 minutes without re-invitation or manual cache clears
API Authorization: Role and Scope Enforcement
Given a client presents an access token without benchmark:read When calling GET /api/benchmarks/regions Then the response is 401 if unauthenticated or 403 if authenticated but unauthorized Given a token with role BenchmarkReader and region claims [NA] When querying multiple regions via the API Then the response contains only NA results and excludes non-permitted regions without revealing their existence Given a token attempts POST /api/exports/benchmarks without export permission When the request is processed Then 403 Forbidden is returned and an audit event reason=missing_permission:export is recorded Given an expired or invalid token When used on any benchmark API endpoint Then the response is 401 with WWW-Authenticate header indicating error=invalid_token
Exports: Aggregation, Redaction, and Scope
Given a user with Exporter role and regions [EMEA] When exporting Region Benchmarks to CSV or BI Then the file contains only aggregate metrics for EMEA; no person- or event-level data is present Given any export cell would represent fewer than privacy_threshold individuals (default 10) When the export is generated Then the cell is suppressed as "<threshold" or bucketed per policy and overall totals remain internally consistent Given a user includes a non-permitted region in export parameters When the export runs Then that region is ignored without disclosure and the output includes only permitted regions Given an export completes When the user downloads it Then the file is watermarked with requester ID, timestamp (UTC), and region scopes Given an export job finishes or fails When logging the result Then an audit event export.generated or export.failed is recorded with checksum, row count (if applicable), and reason on failure
Privacy Thresholds and PII Minimization
Given any Region Benchmarks UI view When rendered Then no PII (names, emails, employee IDs) is displayed or retrievable via DOM or network responses Given a slice would result in a group size < privacy_threshold When a user attempts to view that slice Then the group is hidden or combined into "Other" and a privacy-threshold-applied indicator is shown Given an admin sets privacy_threshold to N (allowed range 5–50) When the setting is saved Then all subsequent queries enforce N within 5 minutes across UI, API, and exports Given an API request includes PII fields When the request is processed Then the response excludes those fields and returns 400 if a PII field is explicitly requested Given time filters narrow populations below threshold When applied Then threshold rules are enforced consistently across all components and channels
Access Logging and Audit Trail
Given any access to Region Benchmarks data (UI, API, export) When the action completes or is denied Then an immutable audit record is written with timestamp (UTC), actor ID, action, resource, scope, outcome, client IP, and user agent Given an auditor with AuditViewer role When querying audit logs by actor, action, and date range Then results for up to 10,000 records return within 5 seconds with pagination Given an attempt to alter or delete existing audit records When attempted Then the operation is rejected and integrity checks (e.g., hash chain/WORM) verify tamper protection Given a compliance export is requested When audit logs are exported Then the file includes a cryptographic checksum and schema version Given a retention policy of 13 months When records exceed retention Then they are purged automatically on schedule and the purge action is itself logged
SSO/SCIM Integration for Roles and Regions
Given SSO/SCIM is configured and a user is added to IdP group "Benchmark-Viewers-EMEA" When provisioning runs Then the user is assigned Regional Viewer with region scope EMEA within 5 minutes Given a user is removed from relevant groups or deactivated in the IdP When SCIM deprovision runs Then the user loses access within 5 minutes and active sessions are revoked on next token validation Given a group-to-region mapping changes from EMEA to EMEA+APAC When synchronization runs Then the user’s scopes update accordingly without manual admin intervention Given SCIM payloads include unsupported attributes When processed Then they are ignored and a 200 response indicates applied changes; malformed payloads return 400 with error details Given an SSO/SCIM outage occurs When detected Then last-known permissions remain in effect, admins are alerted, and a full resync reconciles drift upon recovery
Data Retention and Expiry Controls
Given an admin sets export retention to 7 days When an export exceeds 7 days age Then the file is permanently deleted and download links return 410 Gone Given a user requests early deletion of an export When the deletion is confirmed Then the file is deleted within 15 minutes and an audit event export.deleted is recorded Given access log retention is configured to 13 months When the retention window advances Then older records are purged daily in batches without degrading query SLA Given backups contain data beyond retention When the nightly purge executes Then those items are excluded from future restore points or redacted during restore Given retention settings are changed When saved Then new policies apply to new data immediately and to existing data at the next scheduled purge

Root‑Cause Trace

Click from a hotspot straight to the series, time windows, and attendee cohorts driving unfairness. See contribution percentages and suggested fixes, then export the attribution table for follow‑ups. Cuts investigation time and focuses teams on the highest‑leverage changes.

Requirements

One-Click Hotspot Drilldown
"As a product or engineering lead, I want to click a fairness hotspot and immediately see which series, time windows, and cohorts are driving it so that I can understand the cause without manual data pulls."
Description

Enable users to click any fairness hotspot in analytics and land on a focused attribution view that decomposes the hotspot across dimensions (meeting series, local time windows, attendee cohorts). Provide intuitive navigation with breadcrumbs, tabs, and cross-filters to pivot between dimensions without losing context. Preserve the original filter state (date range, team, metric definition) and show a summary header with metric values and deltas. Support sorting, pagination, column customization, and accessible keyboard navigation. Enforce permissions so users only see series and cohorts they are authorized to view. Emit telemetry for clicks, pivots, and dwell time to measure feature adoption and identify UX friction.

Acceptance Criteria
Hotspot Drilldown Landing and Decomposition
Given a user is viewing the fairness analytics hotspot chart with an applied filter state (date range, team, metric definition) When the user clicks a hotspot data point Then the user is navigated to a focused attribution view within 2.0 seconds at the 95th percentile And the view defaults to show decomposition across meeting series, local time windows, and attendee cohorts as selectable tabs And a summary header displays metric name, hotspot value, baseline value, absolute delta, percent delta, and the inherited date range/team And all displayed metric values match the source hotspot within 0.1% (tolerance) and use the same rounding rules And the URL includes a drilldown context (hotspot_id and a filter snapshot) enabling exact state restoration
Context & Filter State Preservation
Given the drilldown is launched from analytics with a specific date range, team, and metric definition When the attribution view loads Then the original filter state is preserved exactly (date range, team, metric definition, and any chart-level filters) When the user navigates back to analytics via breadcrumb or browser back Then the previously selected chart, viewport, and highlighted hotspot remain intact without requiring manual re-selection When the user shares the attribution view URL with a colleague who has equal permissions Then the colleague opens to an identical state (header values, active tab, filters, and table state)
Dimension Pivots with Breadcrumbs and Cross-Filters
Given the user is viewing the attribution view on any dimension tab When the user pivots dimensions via tab switch (e.g., Time Window to Series) Then existing cross-filters remain applied and are reflected as filter chips And the breadcrumb updates to reflect drill path and allows one-click navigation to prior levels And the URL updates to encode the active dimension and filters without a full page reload And table sort and pagination are preserved when returning to a previously visited dimension within the session
Table Interactions & Accessibility
Given the attribution table is visible When the user clicks a sortable column header Then sort toggles Ascending -> Descending -> None, a visual indicator shows state, and numeric columns sort numerically And the selected sort persists across pagination and within the same dimension until changed When the user paginates Then page sizes of 25/50/100 are available (default 50), navigation works, and page index is preserved in the URL When the user customizes columns Then columns can be shown/hidden and re-ordered, and preferences persist per user and dimension; a Reset to Default option restores system defaults Accessibility: All interactive elements are reachable via keyboard (Tab/Shift+Tab), Enter/Space activates controls, Escape closes menus, focus is visible, aria attributes (e.g., aria-sort) are present, and contrast meets WCAG 2.1 AA
Permissions & Data Security
Given a user lacks access to one or more meeting series or attendee cohorts When viewing the attribution table and aggregates Then unauthorized rows are omitted or redacted and aggregates/percentages recompute over authorized data only And the summary header reflects only authorized data and matches table aggregates within 0.1% When the user attempts to deep-link to an unauthorized entity Then the API returns 403 and the UI displays a non-revealing authorization message without leaking entity existence And all API requests are scoped to the user's tenant/team; cross-tenant data is never returned (verified by automated tests with seeded data)
Telemetry Coverage for Drilldown Interactions
Given analytics and attribution views are instrumented When a user clicks a hotspot Then an event hotspot_drilldown_click is emitted with properties: hashed_user_id, org_id, hotspot_id, metric, date_range, team_id, timestamp, client_version, correlation_id; delivered within 2s with ≥99% success When a user changes dimension tabs or applies/removes a cross-filter Then an event attribution_pivot is emitted with old_dimension, new_dimension, filters_diff, and prior_sort/page_state When the user remains on the page or exits Then an event attribution_dwell is emitted capturing duration_ms with clock-skew-safe timing; sample rate 100% (configurable) All telemetry excludes PII beyond allowed identifiers, includes consent flag, and appears in analytics within 5 minutes (p95 pipeline SLA)
Export Attribution Table
Given the attribution view has active filters, sort, visible columns, and applicable permissions When the user clicks Export and selects CSV Then the download begins within 3 seconds and includes only authorized rows, respects active filters and sort order, includes only visible columns, contains a header row, and uses ISO-8601 timestamps And if the result exceeds 10 MB, an async export is initiated; the user is notified, receives a secure link within 2 minutes, and the link expires after 24 hours and requires auth And an export_attribution telemetry event is emitted with row_count, columns, filters, duration_ms, and success/failure And exported aggregates match on-screen aggregates within 0.1% tolerance
Multi-Dimensional Attribution Engine
"As a data-informed manager, I want clear percentage contributions by series, time windows, and cohorts so that I can quantify which levers will most reduce unfairness."
Description

Compute contribution percentages to the selected fairness metric by meeting series, local time window buckets, and attendee cohorts. Accept the current metric configuration (e.g., after-hours burden index) and produce a normalized attribution table where contributions sum to 100% within scope. Handle overlapping signals by applying a transparent allocation method (e.g., Shapley-like or proportional allocation) documented in the UI. Support incremental recomputation on data changes (new meetings, updated work windows) and nightly full recomputes for accuracy. Ensure determinism, include confidence indicators for sparse data, and expose a stable schema (dimension keys, names, IDs, contribution %, counts, metric values, confidence). Provide APIs for the UI and export, with proper caching and pagination for large datasets.

Acceptance Criteria
Normalized Attribution Within Scope
Given a selected fairness metric configuration and scope filters (date range, teams, regions) When the attribution engine computes contributions by dimension Then the sum of contribution_percent across all rows in each requested breakdown equals 100.00% ± 0.01 percentage points And every contribution_percent is within [0, 100] and numeric And rounding to two decimals preserves the 100% total within ± 0.01 percentage points And filtered-out or null dimensions are excluded from both rows and totals
Dimension Coverage: Series, Time Buckets, Cohorts
Given input data includes meeting series identifiers, localized timestamps, and attendee cohort memberships When computing attribution with breakdowns by series, time_window_bucket, and attendee_cohort Then the response includes three breakdowns matching the requested dimensions And each row includes a non-empty dimension_type, dimension_id, dimension_name And each row includes non-null count, metric_value, and contribution_percent And dimensions with zero exposure in scope are omitted from the result
Transparent Allocation for Overlaps
Given meetings contribute to multiple overlapping signals When allocation is applied using the configured method Then allocation_method is one of [shapley_like, proportional] And allocation_notes describe the method and parameters in plain language And per-row allocated contributions sum to 100% within each breakdown And switching allocation_method changes per-row contributions while keeping totals constant And repeated runs with the same inputs and method yield identical allocations
Incremental Recompute Correctness and Determinism
Given identical inputs and configuration When the engine recomputes incrementally or on-demand Then outputs are deterministic: identical values and ordering across runs And ordering is stable: rows sorted by contribution_percent desc, then dimension_id asc Given a change set (new/canceled meetings or updated work windows) When incremental recompute runs Then only affected rows are recalculated; unaffected rows retain identical values and snapshot_version And a change_log entry is recorded with counts of updated rows
Nightly Full Recompute and Consistency
Given the nightly schedule at 02:00 UTC When the full recompute job runs Then a complete snapshot is produced with snapshot_version=YYYYMMDD and job metadata And caches are swapped atomically to prevent mixed-version responses And for unchanged inputs, full snapshot values differ from the latest incremental snapshot by ≤ 0.1 percentage points per row And job status, duration, and row counts are queryable via /jobs/{id}
Confidence Indicators for Sparse Data
Given variability in sample sizes across dimensions When confidence is computed Then each row includes confidence in [0,1] with two-decimal precision And confidence=0 when count=0 And confidence is non-decreasing with increasing count for fixed variance And rows below min_sample_size include sparsity_flag=true and are still returned with contribution_percent computed And the confidence_method and parameters are included in response metadata
Stable Schema, API, Caching, Pagination, and Export
Given clients request attribution results via API or export When calling GET /attribution with metric_config_id, scope, and dimension parameters Then the response schema includes snapshot_version, allocation_method, allocation_notes, total_count, next_cursor, rows[] And each row includes dimension_type, dimension_id, dimension_name, count, metric_value, contribution_percent, confidence And pagination supports limit and next_cursor; requesting with next_cursor returns the next page with no duplicates; total_count remains consistent And ETag/If-None-Match is supported; unchanged snapshots return 304 And export endpoints provide CSV and Parquet with identical columns and ordering, streamable for up to 5,000,000 rows
Fix Suggestions with Impact Simulation
"As a meeting owner, I want actionable suggestions with estimated impact so that I can quickly choose changes that meaningfully reduce after-hours burden."
Description

Generate ranked suggestions that target the highest-contributing drivers (e.g., shift recurring series by 30 minutes, rotate host timezone, expand overlapping work window by 15 minutes, redistribute attendees). For each suggestion, estimate the projected change to the fairness metric, show affected participants and time windows, and indicate prerequisites or risks. Allow users to apply a suggestion via one click to prefill a scheduling change workflow in TimeTether (without auto-sending invites) and to simulate alternative parameters (what-if adjustments) before committing. Capture rationale and allow dismiss/accept with tracking for later review. Respect permissions and never reveal hidden participant details to unauthorized viewers.

Acceptance Criteria
Ranked Suggestions Display by Projected Fairness Impact
Given a hotspot with at least three actionable drivers and a computed fairness baseline When the user opens the Fix Suggestions panel Then the system displays a list of suggestions ordered by projected fairness improvement in descending order And at least three suggestions are shown if available And each suggestion visibly includes: projected change to the fairness metric (absolute and percentage), the targeted driver label, affected participant count, and affected time windows count
Interactive What‑If Simulation of Parameters
Given a suggestion is selected in the Fix Suggestions panel When the user modifies parameters (e.g., shift by 15/30/45 minutes, rotate host timezone, expand overlapping work window by 15 minutes, redistribute attendees) and clicks Simulate Then the projected fairness change, affected participants, and time windows recalculate and update within 2 seconds And simulated values are clearly marked as hypothetical and are not persisted until Apply or Save And a Reset control restores the suggestion to its original parameters
Apply Suggestion Prefills Scheduling Workflow Without Sending
Given a suggestion is visible to the user When the user clicks Apply on the suggestion Then the scheduling change workflow opens with prefilled fields (series, dates/times, parameter adjustments, and participant set) derived from the suggestion And no calendar invites or notifications are sent until the user explicitly confirms in the workflow And an audit record is created capturing actor, suggestion ID, timestamp, and prefill parameters
Prerequisites and Risks Visibility and Enforcement
Given a suggestion includes prerequisites or risks When the suggestion is displayed Then a dedicated section lists each prerequisite or risk with a clear status indicator (met/unmet) And if any prerequisite is unmet or the user lacks required permissions, the Apply action is disabled and a non-revealing reason is shown And risks include a brief description of potential side effects and any required approvals
Privacy‑Respecting Visibility of Affected Participants and Time Windows
Given organization privacy rules and the viewer’s role/permissions When a suggestion displays affected participants and time windows Then only authorized participant details (e.g., name, email, timezone) are shown; unauthorized details are redacted or aggregated (e.g., “3 hidden participants”) And attempts to view hidden details are blocked with a permission error without leaking any identifiers And all displays avoid exposing private working hours for unauthorized viewers
Accept/Dismiss with Rationale and Review Tracking
Given a suggestion is presented to the user When the user chooses Accept or Dismiss Then the system requires a rationale text of at least 10 characters and allows selection of optional reason codes And the decision, rationale, actor, timestamp, and suggestion ID are persisted and visible in a review history And dismissed suggestions remain hidden from the default list for the same hotspot unless filters include Dismissed or the underlying drivers materially change
Cohort Definition and Governance
"As an admin, I want governed, versioned cohorts so that attribution remains consistent and trustworthy across analyses and over time."
Description

Provide configurable attendee cohorts used for attribution (timezone band, region, role, team, seniority) with the ability for admins to define custom cohorts via rules on user attributes. Support versioned cohort definitions with effective dates to ensure historical analyses remain reproducible. Validate cohort coverage and exclusivity rules, show membership counts, and flag users not mapped to any cohort. Integrate with directory data sources for attributes and handle drift via periodic sync and change logs. Expose a data dictionary and governance controls (who can create/edit, approval workflow) to maintain consistency across teams.

Acceptance Criteria
Rule-Based Custom Cohort Creation With Live Preview
Given an Org Admin with Cohort Steward permission When they create a new custom cohort via the rule builder Then they can add rules using operators =, !=, in list, contains, regex, and numeric range on attributes timezone_band, region, role, team, seniority, and directory-synced custom attributes And When rules are edited Then the membership count and a 20-sample member list update within 2 seconds based on the current active user base And When saving Then the system enforces unique cohort name, requires a description, stores the cohort as Draft, and records version 1.0 with created_by and created_at And When rules match zero users Then activation is blocked but Draft save is allowed with a warning banner And When an invalid attribute or operator is used Then save is blocked with field-level error messaging
Versioned Cohort Definitions and Effective Dating for Reproducible Analytics
Given an existing Active cohort version When a Steward creates a new version and sets an effective_start datetime (timezone-aware) Then the prior version becomes immutable and remains applicable for analyses with as_of dates before effective_start And When effective_start is in the future Then the new version is Scheduled and auto-activates at that time without downtime And When analytics (UI or API) are requested with an as_of date Then the cohort membership used corresponds to the version active at that as_of date And When no effective_start is provided Then activation is immediate and logged with version notes And Then all versions are listed with version number, status (Draft, Scheduled, Active, Deprecated), effective window, author, and change summary
Coverage and Exclusivity Validation Report for Required Taxonomies
Given a cohort taxonomy marked Exclusive and Required (e.g., Timezone Band) When validation is run on demand or nightly at 02:00 UTC Then each active user maps to exactly one cohort in that taxonomy or is flagged as unmapped/multi-mapped And Then the report displays counts: total users, per-cohort members, unmapped users, multi-mapped users, and percentages And Then users failing coverage/exclusivity are listed with reasons and conflicting cohorts/rules And When exporting the report Then a CSV is generated including user_id, email, failing taxonomies, detected_at, and suggested resolution hints And When coverage or exclusivity fails Then activation of new versions for that taxonomy is blocked until errors are resolved or explicitly waived by an Org Admin with justification captured
Directory Sync and Drift Alerts Across Cohort Memberships
Given a configured directory connector (Okta, Azure AD, or Google Workspace) When sync runs on the configured schedule (minimum interval 1 hour) Then user attributes are upserted per field mappings and the last sync status, duration, and record counts are displayed And Then a change log records attribute before/after, user_id, source system, detected_at, and affected cohorts for each change, retained for at least 365 days And When a sync causes cohort membership changes exceeding 25 users or 2% of active users in any taxonomy (whichever is greater) Then an alert is sent to configured channels (email and Slack) within 10 minutes including a link to the diff report And When a sync error occurs Then retries follow exponential backoff up to 5 attempts and failures are surfaced in UI/API with remediation guidance And When a Steward runs a dry-run sync Then a diff report is generated without committing changes and can be downloaded as CSV
Governance Roles and Four-Eyes Approval Workflow for Cohort Changes
Given role assignments (Org Admin, Cohort Steward, Approver) When a Steward proposes a new cohort or a new version Then it is saved as Draft and cannot become Active without approval from a designated Approver distinct from the author And Then Approvers can Approve, Request Changes, or Reject with required comments; all actions are timestamped and audit-logged And When approved Then the cohort/version transitions to Scheduled or Active per its effective date and notifications are sent to watchers via email and Slack And When a user lacks permission for create/edit/approve Then UI actions are hidden or disabled and API calls return 403 with an error code
Data Dictionary Publication for Cohort Rule Attributes
Given authenticated access When a user opens the Data Dictionary Then it lists all user attributes available for cohort rules with fields: name, description, data type, source system, refresh cadence, allowed/example values, nullability, and last updated And Then the dictionary supports search by name and filter by source/data type, returning results within 500 ms p90 And When exporting Then CSV and JSON downloads are provided with a generation timestamp and match on-screen fields And Then each attribute details lineage to directory fields and links to cohorts that reference it
Exportable Attribution Table
"As a program coordinator, I want to export the attribution table with context so that I can share and follow up with stakeholders outside the tool."
Description

Allow users to export the attribution results (contributions by series, time windows, cohorts) as CSV or XLSX with applied filters, timestamp, metric definition, and cohort version metadata embedded. Include stable identifiers and human-readable labels, plus optional PII minimization (hash or omit emails) based on role and export policy. Support large exports via asynchronous jobs with email or in-app notifications and download links that expire. Ensure locale-aware number and time formatting, maintain user local time semantics in time window labels, and record an audit trail of exports (who, when, scope).

Acceptance Criteria
Export CSV with Filters and Metadata
Given a user has applied filters to series, cohorts, and time windows in Root‑Cause Trace When the user selects Export -> CSV and the file downloads Then the CSV contains only rows that match the applied filters And the header includes columns: series_id, series_name, cohort_id, cohort_label, time_window_id, time_window_label, contribution_percentage, contribution_count, metric_name, metric_version, cohort_version, exported_at, filter_summary And time_window_label reflects the user's local time semantics And numbers (contribution_percentage, contribution_count) are formatted per the user's locale settings And exported_at is an ISO‑8601 timestamp And the file name matches timetether_attribution_YYYYMMDDThhmmssZ.csv
Export XLSX with Metadata Sheet and Labels
Given a user selects Export -> XLSX for the attribution table When the workbook is generated and opened Then it contains a sheet named "Attribution" with columns: series_id, series_name, cohort_id, cohort_label, time_window_id, time_window_label, contribution_percentage, contribution_count And it contains a sheet named "Metadata" with fields: metric_name, metric_version, cohort_version, exported_at, filter_summary, user_locale, user_timezone And contribution_percentage cells are numeric with two decimal places (not text) And the number of data rows equals the number of rows shown in the filtered UI And time_window_label uses the user's local time semantics
PII Minimization per Role and Export Policy
Given export policy pii_minimized_export=true or the requesting user lacks the "export_pii" permission When an attribution export is generated Then no plain‑text email addresses appear in any column And if an email column is present it contains only hashed values matching regex ^[A‑Fa‑f0‑9]{32,}$ (and contains no '@') And user display names (if present) contain no email substrings And metadata indicates pii_policy_applied=true Given export policy allows PII and the requesting user has the "export_pii" permission When an attribution export is generated Then the attendee_email column is present with plain‑text emails And metadata indicates pii_policy_applied=false
Asynchronous Large Export with Notifications and Expiring Link
Given the estimated export size exceeds the configured threshold (e.g., >100k rows or >50MB) When the user initiates an export Then an asynchronous job is created and the UI shows status (Queued, Processing, Completed, Failed) with a job_id And upon completion the user receives an in‑app notification and, if email notifications are enabled, an email with a download link And the download link expires after the configured TTL and returns 403/410 after expiry And on job failure the user receives a failure notification containing a retry action And the exported file, when downloaded before expiry, reflects the applied filters
Locale‑Aware Numbers and Time Window Labels
Given a user with locale de-DE and timezone Europe/Berlin exports the attribution table When the file is opened Then contribution_percentage values use decimal comma and thousands separator (e.g., 1.234,56) And time_window_label shows local day and time (e.g., "Mo 09:00–10:00 (Europe/Berlin)") And metadata includes user_locale=de-DE and user_timezone=Europe/Berlin Given a user with locale en-US and timezone America/Los_Angeles exports the attribution table When the file is opened Then numbers use decimal point and comma thousands (e.g., 1,234.56) And time_window_label shows local time (e.g., "Mon 9:00 AM–10:00 AM (America/Los_Angeles)")
Audit Trail of Exports
Given any export (CSV or XLSX) is initiated When the export completes or fails Then an audit record is written that includes: user_id, user_email, occurred_at (ISO‑8601), format (CSV/XLSX), scope (filters applied summary, metric_version, cohort_version), row_count (if successful), pii_policy_applied, job_id (if async), outcome (success/failure) And the audit record is retrievable in the audit log by an administrator filtered by user and date range
Stable Identifiers and Label Consistency
Given an attribution export is generated When inspecting any row in the export Then the row includes both stable identifiers and human‑readable labels for series, cohort, and time window (e.g., series_id and series_name, cohort_id and cohort_label, time_window_id and time_window_label) And identifier values match the corresponding objects returned by the attribution API for the same filter set And column names and order are consistent across CSV and XLSX exports And the default row order matches the order shown in the UI
Traceability and Audit Panel
"As a stakeholder, I want transparent traceability of calculations so that I can trust the results and justify decisions to my team."
Description

Provide an in-context panel that explains how the fairness metric and contributions were computed, including formula references, data sources, time ranges, cohort version, and allocation method. Allow users to drill into sample underlying meetings and attendance records with redacted PII as required by permissions. Display computation timestamp, data freshness, and known limitations. Generate a unique analysis ID to reference in tickets and exports, ensuring analyses are reproducible. Log user interactions for compliance and create an audit trail for governance reviews.

Acceptance Criteria
Open Traceability Panel from Hotspot
Given a user is viewing a Root‑Cause Trace hotspot with an available analysis When the user clicks "Traceability & Audit" Then an in-context side panel opens within 500 ms And the panel header displays the fairness metric name, cohort name, cohort version, allocation method, and a unique Analysis ID And the panel displays the computation timestamp in the user's local timezone and a relative data freshness indicator
Display Formula References and Known Limitations
Given the Traceability & Audit panel is open for an analysis When the user expands the Computation Details section Then the fairness metric formula is shown in human-readable form and includes the metric version And a link to the versioned formula reference documentation is visible and opens successfully (HTTP 200) in a new tab And the allocation method is labeled with its version/identifier And a Known Limitations section is present, shows last-updated date, and links to the limitations documentation (HTTP 200)
Show Data Sources, Time Range, and Freshness
Given the Traceability & Audit panel is open When the user views the Data Sources section Then each data source used in the analysis lists connector name, dataset identifier, schema version, and as-of timestamp And the analysis time range (start and end) is displayed in ISO 8601 and in the user's local timezone And if any source freshness age exceeds the organization threshold, a warning badge and tooltip indicate staleness
Drill Into Redacted Meeting and Attendance Samples
Given the Traceability & Audit panel is open When the user clicks "View Samples" Then a modal displays at least 10 sample records with columns: meeting ID (hashed), series ID (hashed), start time, end time, attendee count, cohort(s), and fairness contribution percentage And PII fields (names, emails) are redacted for users without PII_VIEW permission And users with PII_VIEW permission can toggle between redacted and unredacted views And users without DATA_DRILL permission see an access denied message and no records And export from the samples modal respects permissions (redacted/unredacted accordingly)
Unique Analysis ID Generation and Propagation
Given an analysis is computed or loaded in the panel Then an Analysis ID that is unique, URL-safe, and immutable is present and matches regex ^[A-Za-z0-9_-]{20,64}$ And a Copy Analysis ID control copies the exact ID to clipboard with a success confirmation And all exports (CSV/JSON) and generated ticket links from the panel include the Analysis ID
Reproducibility with Stable Inputs
Given identical inputs (fairness metric version, allocation method version, cohort definition version, data source snapshot timestamps, and time range) When the analysis is recomputed Then the same Analysis ID is produced And all fairness outputs match the prior analysis within an absolute tolerance of ≤ 0.0001 And changing any one of those inputs results in a different Analysis ID
Compliance Audit Trail for Panel Interactions
Given a user performs any of these actions: open panel, expand/collapse details, view samples, export, copy Analysis ID, open formula link Then an immutable audit log entry is created containing: org ID, user ID, role, timestamp (UTC), analysis ID, action type, action target, result (success/failure), and client IP hash And audit entries are available to authorized auditors (AUDIT_VIEW permission) via query by date range and analysis ID And log entries are retained for at least 365 days and become queryable within 2 minutes of the action And all access to audit logs is itself logged with the same fields
Performance and Caching SLA
"As a busy lead, I want the root-cause view to load instantly so that I can act during the same workflow without waiting."
Description

Meet interactive performance targets for Root‑Cause Trace: initial drilldown view loads in under 2 seconds at the 95th percentile for typical team scopes, and under 5 seconds at the 99th percentile. Use precomputation and incremental refresh, result caching by filter tuple, and background warm-ups for recently viewed hotspots. Implement graceful degradation with skeleton loaders and progressive data hydration for large tables. Monitor latency, cache hit rates, and error budgets, triggering alerts when SLAs are at risk. Ensure horizontal scalability and safe cache invalidation on data changes (meetings, work windows, cohorts).

Acceptance Criteria
Initial Drilldown Latency SLA (Cold Cache, Typical Scope)
Given a team scope with <= 100 attendees, <= 75 recurring series, and a 90-day window, and the filter tuple is not present in cache When a user clicks a hotspot to open the Root-Cause Trace initial drilldown Then time-to-interactive for KPIs and top drivers is <= 2.0 seconds at the 95th percentile over 500 requests And time-to-interactive is <= 5.0 seconds at the 99th percentile And error rate for these requests is <= 0.5%
Warm Cache Latency and Hit Rate (Recently Viewed Hotspots)
Given a hotspot viewed within the last 30 minutes and an identical filter tuple, with background warm-up enabled When the user re-opens the same drilldown Then cache hit rate for these requests over 1 hour is >= 80% And response latency is <= 1.0 second at p95 and <= 2.0 seconds at p99 And all cache entries served are <= 15 minutes since the last relevant data change
Progressive Hydration and Skeleton Loaders for Large Tables
Given an attribution table with > 5000 rows or > 100 kB serialized payload When the user opens the drilldown Then a skeleton loader appears within 150 ms of click And KPIs and header render by 2.0 seconds at p95 And the table hydrates progressively in batches not exceeding 500 rows per chunk, with main-thread long tasks > 50 ms occurring in <= 1% of frames during hydration And first input delay during hydration is <= 100 ms at p95 And a visible loading indicator remains until hydration completes
Precomputation and Incremental Refresh Freshness SLO
Given any new or updated meeting, change to work windows, or cohort membership change When the change is committed to the source of truth Then affected precomputed aggregates are rebuilt and corresponding cache keys invalidated within 60 seconds at p95 and 180 seconds at p99 And end-user drilldown reflects the change within 2 minutes at p95 and 5 minutes at p99 And unrelated cache keys remain valid, with <= 1% collateral invalidations over a 24-hour window
Safe Cache Invalidation on Data Changes
Given a change touching a specific series, time window, or cohort When cache invalidation executes Then only cache keys whose filter tuple intersects with the changed entities are invalidated And an audit log records the invalidated keys and lineage for >= 95% of invalidations And a post-invalidation read returns updated values consistently across 3 consecutive reads And there are 0 stale reads > 5 minutes old across 1000 change-to-read test runs
Monitoring, Alerting, and Error Budget Burn
Given production traffic and 1-minute metric aggregation When service-level indicators are computed Then latency (p50, p95, p99), cache hit rate, error rate, and refresh lag are exported to monitoring And alerts trigger within 2 minutes when 10-minute rolling p95 > 2.0 s or p99 > 5.0 s for typical-scope drilldowns And alerts trigger when warmed-hotspot cache hit rate < 60% for 10 consecutive minutes And alerts trigger when error budget burn rate exceeds 2.0x over the last hour And alerts notify the on-call channel and include a runbook link
Horizontal Scalability Under 4x Traffic Spike
Given a synthetic load of 4x baseline drilldown request rate with 200 concurrent users across 10 typical team scopes and a 20% cache miss rate When auto-scaling is enabled during a 15-minute spike Then the service maintains drilldown latency of <= 2.5 s at p95 and <= 5.5 s at p99 And queue wait time is <= 200 ms at p95 And error rate is <= 1% And capacity scales back down within 10 minutes after traffic normalizes

Audit Pack

One‑click, shareable audit bundle that assembles charts, CSV extracts, breach summaries, and a plain‑language narrative of progress and risks. Watermarked, versioned, and expiration‑controlled links keep reviews tidy and compliant. Saves hours preparing quarterly or board updates.

Requirements

One-Click Audit Bundle Generation
"As a head of product, I want to generate a complete audit pack with one click so that I can deliver consistent board updates without manual compilation."
Description

Generate a complete audit pack from a single action in the TimeTether UI or via API, assembling a PDF report with KPI charts, CSV extracts, breach summaries, and a plain-language narrative for a selected time range and team scope. Users pick presets or custom filters; a background job compiles assets, stores them, and returns a shareable link. The bundle uses consistent branding, time normalization, and metadata, integrates with existing analytics pipelines, and supports retries and notifications when ready. Expected outcome: standardized, reproducible audit deliverables that save hours and reduce manual error.

Acceptance Criteria
UI One-Click Generation with Presets and Filters
Given a user with permissions selects a team scope and time range and chooses a preset or custom filters, When they click "Generate Audit Pack," Then a background job is queued and the UI displays a job ID and pending status. Given a valid job is queued, When the job completes, Then the user receives an in-app notification and an email containing the shareable link and bundle metadata summary. Given required inputs are missing or invalid, When the user attempts to generate, Then a descriptive validation error is shown and no job is queued. Given duplicate submissions with identical parameters within a short interval, When the user clicks Generate again, Then the UI prevents duplicate job creation and references the existing job status.
API Generation and Webhook Notification
Given a POST to /api/audit-packs with team scope, time range, filters, and an Idempotency-Key, When the request is valid, Then the API responds 202 with job_id and a status endpoint. Given the same Idempotency-Key and identical payload are posted within 24 hours, When the API receives the request, Then it returns the original job_id without creating a new job. Given a webhook_url is provided, When the bundle is ready, Then a signed webhook is sent with status=ready, link, checksums, and parameters; non-2xx responses are retried with exponential backoff up to 3 times. Given webhook signing is enabled, When the consumer verifies, Then the payload includes timestamp and HMAC-SHA256 signature headers that validate against the shared secret.
Background Compilation and Storage Finalization
Given analytics pipelines are available, When the job runs, Then it assembles KPI charts, CSV extracts, breach summaries, and a narrative for the selected scope and time range. Given assets are successfully generated, When stored, Then they are written under a single immutable, versioned bundle with a manifest that records parameters, generator version, and per-asset checksums. Given a bundle is being built, When assets are partially written, Then the shareable link remains inaccessible until the manifest is finalized and marked complete (atomic publish). Given identical inputs (time range, scope, filters, generator version), When two jobs run, Then produced assets are byte-identical except for system metadata fields (e.g., generated_at).
Shareable Link: Watermark, Versioning, Expiration, and Access Control
Given a completed bundle, When a shareable link is created, Then it includes a version identifier, default expiration (org-configurable), and enforces watermarking on all PDF pages with viewer identity and access timestamp. Given a link is expired or revoked, When accessed, Then a 403 is returned and no asset bytes are served; the landing page displays "Link expired or revoked." Given a newer version exists for the same scope/period, When an older link is accessed, Then the page indicates it is superseded and, if permitted, offers navigation to the latest version. Given access logging is enabled, When the link is accessed, Then an audit log entry is recorded with timestamp, viewer identity (if known), IP, and user agent.
Branding, Time Normalization, and Metadata Consistency
Given org branding is configured, When generating the PDF, Then the report uses the org logo, color palette, typography, and footer legal text consistently across sections. Given the team's primary timezone is set, When rendering all timestamps in charts, tables, CSVs, and narrative, Then times are normalized to that timezone and the UTC offset is disclosed on the cover and in CSV headers. Given the bundle metadata panel, When inspected, Then it lists generator version, data snapshot window, filters applied, team scope identifiers, time normalization details, and per-asset checksums. Given DST transitions occur within the selected window, When computing day boundaries and buckets, Then the output respects local DST rules without gaps or double counting.
Retries, Fault Tolerance, and Error Reporting
Given transient upstream failures (e.g., analytics timeout) occur, When the job runs, Then failed steps are retried with exponential backoff up to 3 attempts before the job is marked failed. Given a job fails, When presented in UI and API, Then status=failed is shown with a human-readable message, error code, and a Retry action that queues a new job with the same parameters. Given a retry later succeeds, When viewing job history, Then prior attempts and outcomes are visible and linked to the final bundle. Given multiple jobs are attempted with identical parameters, When idempotency is detected, Then the system links to the existing successful bundle rather than regenerating content.
Deliverable Contents and Format Validation
Given a completed bundle, When downloaded, Then the PDF contains cover, KPI charts with legends and data sources, breach summaries with counts and severities, and a plain-language narrative of progress and risks. Given CSV extracts are included, When opened, Then each CSV has a header row, UTF-8 encoding, consistent delimiter, documented columns in metadata, and row counts that reconcile with figures in the PDF. Given the narrative is generated, When reviewed, Then it references the correct time range and team scope and contains no placeholders or empty sections. Given integrity verification is requested, When computing checksums, Then manifest checksums match stored assets and the share page displays a Verified indicator when all checks pass.
KPI Charts & Metrics Snapshot
"As an engineering director, I want clear charts of our scheduling impact so that stakeholders can grasp trends and outcomes at a glance."
Description

Automatically produce high-resolution charts that capture TimeTether’s core scheduling KPIs for the selected period: after-hours meeting rate, scheduling lead time, fairness rotation balance, conflict avoidance count, timezone distribution, and meeting burden per person. Charts include prior-period comparisons, targets, and annotations, and are embedded in the PDF and available as PNG/SVG assets. Data is sourced from the analytics service with caching and deterministic queries to ensure repeatability. Expected outcome: clear visual evidence of progress and risks aligned to executive reporting standards.

Acceptance Criteria
Period Selection & Deterministic Repeatability
Given tenant "Acme" selects reporting period P (e.g., 2025-06-01 to 2025-06-30 UTC) in Audit Pack When KPI Charts are generated twice: first with cache cold and then again within 5 minutes after completion Then the analytics query parameter signature (tenant, period start/end, timezone, version) is identical across runs And all KPI numeric values are exactly equal across runs And all generated PNG and SVG assets have identical SHA-256 hashes across runs And the embedded chart image streams in the PDF are binary-identical across runs And the snapshot version tag is identical across runs
KPI Coverage & Metric Accuracy
Given the analytics service returns dataset D for tenant X and period P containing: after_hours_rate, scheduling_lead_time_hours, fairness_rotation_balance, conflict_avoidance_count, timezone_distribution, meeting_burden_per_person When KPI Charts are generated Then there is a distinct chart for each KPI (6 total) with the correct title and unit displayed And rendered KPI values match D with rounding rules: percentages to 0.1%, hours to 0.1h, counts exact, distributions sum to 100% ±0.1 And meeting_burden_per_person chart includes all members (or top 20 with “+N more” aggregate if >20) and totals equal the underlying dataset within 0.1 And timezone_distribution bins shown match the distribution keys from D and the total sample size is labeled
Prior-Period Comparison & Targets Visualization
Given org targets are configured (or explicitly unset) and a prior period P− of equal length to P exists immediately before P When KPI Charts are generated for P Then each chart displays current value, prior-period value, and delta with an up/down arrow and unit-aware formatting (%, h, count) And a target line/band is rendered per KPI using org targets; chips are color-coded: green if meets/exceeds target, amber if within 10% of target threshold, red otherwise And if a KPI has no target configured, a “No target set” label appears and no color coding is applied And tooltips disclose current, prior, delta, and target values with the same rounding rules
Annotations on Charts
Given a user adds per-chart annotations (plain text, max 280 chars) pinned to a date or segment prior to generation When KPI Charts are generated Then each annotation appears at the correct position with author and timestamp in a tooltip/callout And annotations are included in the PDF and in the exported PNG and SVG assets And HTML/script input is escaped; unsupported markup is stripped; newlines are preserved And annotations longer than 280 chars are truncated with an ellipsis without breaking UTF-8 characters
Asset Export & PDF Embedding Quality
Given KPI Charts have been generated for period P When assets are exported Then each PNG is at least 1920px width at 2x scale and ≤1.5 MB per chart; each SVG is valid XML and ≤500 KB per chart And charts are embedded into the Audit Pack PDF at 300 DPI equivalent with vector text preserved (no unintended rasterization) And assets are downloadable via signed, expiration-controlled URLs (default 7 days) with filename pattern kpi_{name}_{period}_{version}.{png|svg} And each asset includes alt text equal to the chart title and meets WCAG AA contrast for lines, text, and targets
Caching, Performance, and Data Freshness
Given this is the first generation for tenant X and period P with empty cache When KPI Charts are generated Then p95 generation time over 50 runs for P ≤ 90 days is ≤60s and p50 is ≤20s And a subsequent request with identical inputs within the cache TTL (24h) returns x-cache=HIT and completes in ≤5s And cache entries invalidate on analytics backfill signals or org settings changes that affect KPIs (targets, work windows) And a “Data as of” timestamp reflects the analytics snapshot time and is shown on each chart and in the PDF
CSV Data Extracts
"As a data analyst, I want clean, well-documented CSV extracts so that I can audit and extend the analysis in my own tools."
Description

Provide machine-readable CSV exports aligned to the audit pack’s scope, including meetings (IDs, organizers, participants, scheduled time, timezones, after-hours flag), conflict resolutions, rotation fairness metrics, and policy breach events. Files include a data dictionary, consistent column naming, UTC normalization, and pagination for large datasets. Exports are checksummed and referenced from the PDF with version metadata for traceability. Expected outcome: analysts can independently verify metrics and run deeper analysis using standardized datasets.

Acceptance Criteria
Dataset Coverage and Schema Consistency
- System produces the following CSVs for the export version: meetings.csv, meeting_participants.csv, conflict_resolutions.csv, rotation_fairness.csv, policy_breaches.csv; an absent dataset yields a header-only CSV. - All CSVs are UTF-8, RFC 4180 compliant, comma-delimited, LF line endings, double-quote escaping, no BOM. - Column headers use snake_case, ASCII alphanumerics + underscore only, stable order across pages and versions. - Required columns per file: meetings.csv: meeting_id, organizer_id, organizer_timezone, scheduled_at_utc, duration_minutes, after_hours_flag, scheduling_policy_version meeting_participants.csv: meeting_id, participant_id, participant_timezone, response_status conflict_resolutions.csv: conflict_id, meeting_id, detected_at_utc, resolution_type, resolved_at_utc, resolved_to_utc rotation_fairness.csv: team_id, period_start_utc, period_end_utc, participant_id, total_meetings_count, after_hours_meetings_count, burden_ratio policy_breaches.csv: breach_id, occurred_at_utc, policy_id, policy_name, meeting_id, severity, summary - Data types and constraints: meeting_id, conflict_id, breach_id are unique per file; duration_minutes is integer >= 0; after_hours_flag is boolean true/false; timezone fields are valid IANA TZ IDs; severity in {low, medium, high, critical}; response_status in {accepted, tentative, declined, no_response}. - No duplicate primary keys; no blank headers; no columns with mixed types; empty values use empty string, not "null".
UTC Normalization and Timezone Accuracy
- All timestamp columns end with _utc and are ISO 8601 format with Z suffix (e.g., 2025-09-01T13:00:00Z); no offsets other than Z. - Organizer and participant timezone columns contain canonical IANA identifiers (e.g., Europe/Berlin), validated against tzdb. - Given a meeting scheduled 2025-09-01 15:00 Europe/Berlin, when exported, scheduled_at_utc equals 2025-09-01T13:00:00Z and organizer_timezone equals Europe/Berlin. - For meetings crossing DST boundaries, UTC conversion matches tzdb rules for the date; 3 sample cases are verified automatically. - All UTC timestamps reflect the final scheduled time after conflict resolution.
Scalable Pagination for Large Exports
- Each dataset endpoint supports pagination via page_size (1–10,000, default 1,000) and page_token parameters. - Results are ordered by the dataset’s primary key ascending and are stable across requests within the same version. - The response includes an HTTP Link header with rel="next" when more pages are available; absence of Link indicates last page. - No row appears on more than one page and no rows are skipped across a full traversal of all pages. - Given 25,500 meetings and page_size=10,000, three pages are returned with counts 10,000, 10,000, 5,500.
Checksums and Integrity Manifest
- A manifest.json accompanies each export version listing for each file: file_name, byte_size, sha256, row_count, generated_at_utc, version_id. - The SHA-256 checksum of each downloaded file matches the manifest entry; mismatches cause a 409 integrity error on validation endpoint. - The manifest.json itself includes its own sha256 in a sidecar manifest.sha256 and is referenced from the PDF. - A HEAD request to any file URL returns X-Checksum-SHA256 and X-Version-ID headers matching the manifest.
Versioning and PDF Traceability
- Each export is immutable and addressed by a version_id (UUID) and generated_at_utc; URLs include version_id. - The Audit Pack PDF contains a “Data Exports” section listing each CSV’s file_name, row_count, and sha256, and the export version_id. - Clicking a link in the PDF downloads the exact versioned file; the file’s checksum matches the PDF-listed sha256. - When a new export is generated, prior PDF links remain accessible and are marked “superseded” in the web UI; version history shows at least the last 12 months.
Comprehensive Data Dictionary
- The export includes data_dictionary.csv with columns: dataset, column, type, description, nullable, allowed_values, example. - Every column present in the CSVs appears exactly once in the data dictionary; no extra undocumented columns exist. - Type values are from the allowed set {string, integer, boolean, timestamp_utc, uuid}; nullable is true/false. - The data dictionary passes a schema check and is referenced in the PDF with its sha256.
Metric Reproducibility From CSVs
- Recomputed After-Hours Meeting Rate from meetings.csv (sum(after_hours_flag)/count(*)) matches the PDF headline metric within ≤0.1 percentage points. - Recomputed fairness burden_ratio per participant from rotation_fairness.csv aggregates to a Gini coefficient matching the PDF within ≤0.005 absolute difference. - Conflict counts derived from conflict_resolutions.csv by resolution_type match the PDF’s counts exactly. - Discrepancies beyond tolerances fail the export with a diagnostic report listing the first 10 mismatches.
Breach Detection & Summary
"As a compliance officer, I want a clear summary of policy breaches so that I can assess risk and prioritize corrective actions."
Description

Detect and summarize policy breaches within the selected time range using configurable rules: after-hours meetings above threshold, fairness deviation beyond tolerance, missed scheduling SLAs, and recurring conflict policy violations. The summary aggregates counts and severities, highlights top contributors, shows trend deltas versus prior period, and links to the rule configuration snapshot used for evaluation. Expected outcome: a concise, defensible risk overview that guides remediation and informs governance.

Acceptance Criteria
Detect After-Hours Meetings Above Threshold
Given a selected time range and per-user work windows with timezones and DST rules And the rule "After-Hours Threshold" is configured as N occurrences per user per selected period When all meetings in the range are evaluated, excluding canceled and fully declined events Then any meeting whose local start time falls outside a participant's defined work window is counted as after-hours for that participant And for each user with after-hours count > N, a breach is recorded under rule "After-Hours" And the summary displays: total after-hours breaches, unique users breached, and the top 5 users by after-hours count
Flag Fairness Deviation Beyond Tolerance
Given a selected time range and fairness tolerance T% configured And each member's after-hours rate is defined as after_hours_meetings / total_meetings in the range And at least 3 team members have meetings in the range When each member's after-hours rate is compared to the team mean rate Then any member with deviation > T% triggers a "Fairness Deviation" breach And if the team mean rate = 0, any member with >0 after-hours meetings triggers a breach with deviation reported as 100% And the summary lists the top 5 members by absolute deviation with their deviation percentages
Identify Missed Scheduling SLAs
Given a selected time range, an SLA threshold of H business hours, and a configured business calendar (working days and holidays) When time-to-schedule is calculated for each meeting from request creation to first accepted final invite Then any meeting with time-to-schedule > H business hours is recorded as an "SLA Missed" breach And the summary shows total SLA breaches and the breach rate = breaches / total requests in the range
Catch Recurring Conflict Policy Violations
Given a selected time range, a conflict tolerance K per series, and a conflict definition of >=15 minutes overlap with a required attendee's accepted event When evaluating all recurring meeting series with instances in the range Then for each series, count instances where any required attendee had a conflicting accepted event overlapping by >=15 minutes at the scheduled time And if count > K, record one "Recurring Conflict Violation" breach for that series with conflict_count = count And the summary lists the top 5 series by conflict_count
Aggregate Counts, Severities, and Top Contributors
Given breaches detected for the selected time range across all rules When generating the Breach Summary Then for each rule, display total breaches, number of affected entities (users or series), and severity distribution by Low/Medium/High And compute an overall severity score using weights Low=1, Medium=3, High=5 and display the total And a Top Contributors section lists up to 5 entities per rule ordered by breach count descending then name ascending And all displayed totals equal the sum of underlying breach instances
Compute Trend Deltas Versus Prior Period
Given a prior period immediately preceding the selected range with equal duration and the same rule scope When computing trend deltas Then for each rule, display absolute change in breach count = current_count - prior_count And display percent change when prior_count > 0 as ((current_count - prior_count) / prior_count) rounded to the nearest whole percent And display "New" when prior_count = 0 and current_count > 0, and "0%" when both prior_count and current_count = 0
Link Rule Configuration Snapshot to Summary
Given breach evaluation is complete for the selected time range When rendering the Breach Summary Then include a link to an immutable, read-only configuration snapshot capturing all rule settings used (thresholds, tolerances, severity weights, work windows, business calendar, timezone rules) And the snapshot link shows version, hash, and timestamp and matches the Audit Pack's version metadata And access via a share link respects the Audit Pack's expiration; expired links return HTTP 410 and are not accessible And the snapshot content is identical on every retrieval for the same version
Plain-Language Narrative Builder
"As a program manager, I want a concise narrative of results and risks so that I can brief leadership efficiently and confidently."
Description

Generate an executive-grade narrative that interprets KPIs, breaches, and deltas in plain language, including highlights, risks, root causes, and recommended actions with references to charts and CSVs. Supports templating, tone controls, and manual edits before finalization. Captures generation parameters and citations for auditability and offers deterministic template-only mode. Expected outcome: a concise, evidence-backed summary suitable for quarterly and board reviews without additional rewriting.

Acceptance Criteria
Executive Narrative Generation from Latest Audit Pack
Given a completed Audit Pack with defined date range, team scope, KPIs, breaches, and deltas When the user clicks "Generate Narrative" with default settings Then the system produces a narrative <= 600 words containing sections: Highlights, Risks, Root Causes, Recommended Actions, each with >= 1 item And each referenced KPI/breach/delta includes plain-language interpretation with metric name, delta direction (increase/decrease), numeric value, and comparison period And the narrative achieves Flesch–Kincaid Grade Level <= 10 and jargon term ratio <= 3% based on the product glossary And generation completes within 8 seconds for datasets up to 200 KPIs, 50 breaches, and 12 weeks of deltas
Deterministic Template-Only Mode Output
Given template-only mode is enabled with Template ID and Version and inputs I When Generate Narrative is executed N=3 times on inputs I Then all outputs are byte-identical and match the approved golden sample for I And the generation trace records exactly 1 template render step and 0 model inference steps And model parameters temperature=0 and seed are recorded and constant
Tone Control Application and Validation
Given tone preset = Neutral When the narrative is generated Then sentiment score is between -0.10 and +0.10, exclamation count = 0, and hedging terms <= 4 per 500 words Given tone preset = Confident When the narrative is generated Then sentiment score is between +0.10 and +0.40, hedging terms <= 1 per 500 words, and passive-voice sentences <= 10% Given tone preset = Candid When the narrative is generated Then the Risks section contains explicit risk statements, hedging terms <= 2 per 500 words, and passive-voice sentences <= 5%
Manual Edit Workflow Before Finalization
Given a generated narrative in Draft state When the user edits content in the editor and clicks Save Draft Then all edits persist with revision history (version n+1), user ID, and timestamp When the user clicks Discard Changes Then the narrative reverts to the last saved version without data loss When the user deletes a sentence containing a citation Then the system flags the orphaned citation and offers automatic removal or reassignment before save
Evidence Citation and Cross-Reference Integrity
Given a generated narrative with KPI/breach/delta references When the narrative is viewed Then 100% of KPI/breach/delta claims include at least one citation that resolves to a Chart ID or CSV Extract ID within the Audit Pack And each citation displays source type, snapshot timestamp, and data checksum When a citation target is missing or expired Then the narrative displays a broken-reference indicator and blocks Finalize until resolved
Generation Parameters Capture and Auditability
Given any narrative generation request When the job completes Then the system stores parameters: template ID+version, tone preset, date range, teams/projects, data snapshot ID, model name+version, temperature, seed (if used), user ID, request timestamp, and environment And a signed checksum of inputs and output is recorded When a user opens Narrative Details Then parameters and checksum are viewable and exportable as JSON When comparing two narrative versions Then a parameter diff highlights all changes
Finalization, Versioning, and Locking
Given a Draft narrative with all citations resolving to snapshot IDs When the user clicks Finalize Then the system creates an immutable version with incremented version number, applies a watermark containing product name, narrative version, and expiration date from Audit Pack, and sets the narrative to Read-Only And Finalize is blocked if any citation is broken, listing specific issues When edits are made after Finalize Then a new version is created (previous versions remain accessible), all versions are timestamped, signed, and include complete parameter and citation records
Watermarked, Versioned Artifacts
"As a security lead, I want watermarked and versioned audit packs so that distribution is controlled and provenance is provable."
Description

Apply organization-specific watermarks with pack ID, version, timestamp, and viewer identity to PDFs and images; embed checksums and maintain immutable version history on updates. Store artifacts in encrypted object storage with tamper-evident metadata and optional digital signatures. Provide a version diff view for narratives and breach summaries. Expected outcome: traceable, compliant artifacts that deter leaks and support audit integrity.

Acceptance Criteria
Dynamic Watermark on PDFs and Images
Given an Audit Pack PDF or image artifact is generated and accessed by an authenticated viewer When the artifact is opened, downloaded, or printed Then a visible, non-removable watermark appears on the content containing organization name, pack ID, version, UTC timestamp, and the viewer’s identity (name or email) Given the artifact is a multi-page PDF When any page is viewed or printed Then the watermark is present on every page and cannot be toggled off (flattened into page content) Given the artifact is an image (PNG or JPEG) When the file is downloaded Then the watermark is rendered directly onto the bitmap and remains visible after re-saving or re-exporting the image
Immutable Version History on Artifact Updates
Given an artifact version V exists When a change is saved to that artifact Then a new version V+1 is created and version V remains read-only and downloadable Given a prior version is requested for modification via UI or API When an update is attempted on that prior version Then the operation is rejected with 409 Conflict and no data is altered Given the version history view is opened When versions are listed Then each entry displays version number, createdAt (UTC), createdBy, and a link to download that exact version
Embedded Checksums and Tamper Verification
Given an artifact version is finalized When the file is stored Then a SHA-256 checksum is computed, stored in metadata, and embedded into the file’s standard metadata (e.g., XMP/EXIF) for PDFs/images Given the artifact is downloaded without modification When the system verifies the file Then the recomputed checksum matches the stored checksum and the verification status is "Verified" Given the artifact is altered externally (byte-level change) When the verification endpoint/UI is used on the altered file Then the checksum does not match and the status is "Tampered", including the expected vs. actual checksum values
Encrypted Storage and Tamper‑Evident Metadata
Given any artifact is stored in object storage When inspecting storage properties via API Then server-side encryption at rest is enabled with a managed key (per-tenant) and encryption headers/metadata are present Given an unauthorized principal attempts to access the stored object When a direct object read is requested Then access is denied (403) and zero bytes are returned Given lifecycle/audit metadata is recorded for create/update/download events When the metadata log is retrieved Then entries are append-only and hash-chained (each record includes previous-hash and current-hash) and pass chain validation Given an attempt is made to alter or delete a historical metadata entry When the admin or public API is used Then the operation is rejected and an immutable rejection event is added to the log
Optional Digital Signatures with Verification
Given tenant policy "Sign artifacts" is enabled and a valid signing certificate is configured When a PDF or image artifact version is generated Then a verifiable digital signature is produced (embedded for PDFs or detached file for images) and is available for download/inspection Given the verification UI/API is used on an unmodified, signed artifact When validation occurs Then the signature is "Valid" and the signer identity and signing timestamp are displayed Given the artifact is modified after signing When validation occurs Then the signature status is "Invalid" and checksum verification also fails
Narrative and Breach Summary Version Diff View
Given two versions of a narrative or breach summary are selected When the diff view is opened Then additions and deletions are highlighted at word-level within lines, with unchanged context preserved Given each version is <= 50,000 characters When rendering the diff Then the view loads within 2 seconds at p95 on a standard test dataset Given the diff is exported as PDF When export is triggered Then the resulting PDF includes the same highlights, a header with compared version identifiers and timestamp, and the standard watermark
Viewer Identity Resolution for Watermarks via Shared Links
Given a shareable access link is bound to recipient identity X and opened without authentication When the artifact is viewed or downloaded Then the watermark displays X as the viewer identity and the access event is attributed to X Given access requires authentication and a user Y signs in via the link When the artifact is viewed or downloaded Then the watermark displays Y as the viewer identity and the access event is attributed to Y Given the access link is expired or revoked When it is opened Then access is denied and no artifact content is returned
Expiring Share Links & Access Controls
"As a team lead, I want to share an audit pack via a secure, expiring link so that external reviewers can access it safely without new accounts."
Description

Create signed, expiring links with configurable TTL, password protection, optional SSO enforcement, download/view limits, and one-click revocation. Enforce least-privilege access by team and pack, log access events, and surface viewer identity on the pack watermark. Links support web viewing and bundle download while honoring expiration and revocation states. Expected outcome: secure, compliant sharing that simplifies external reviews without account provisioning.

Acceptance Criteria
Create Signed Expiring Link with Configurable TTL
Given a pack owner selects an Audit Pack and configures a TTL When they generate a share link Then the system issues a signed URL with an embedded expiry matching the configured TTL to the minute And the link allows web viewing and bundle download until the expiry time And server-side signature validation denies any tampered URL And at or after expiry, both viewing and download return an expiry error without exposing content And the link detail shows the exact expiry timestamp in the creator’s timezone and UTC
Password-Protected Share Link
Given a pack owner enables password protection and sets a password When a viewer opens the link Then the system requires the password before revealing any content And incorrect passwords are rejected with a non-disclosing error and attempts are rate-limited And the password is stored hashed and never included in URLs or audit exports And updating the password takes immediate effect and invalidates the prior password
SSO-Enforced Access Gate
Given a pack owner enables SSO enforcement for the link and the tenant has an IdP configured When a viewer opens the link Then the viewer is redirected to authenticate via the configured IdP And only identities from the permitted tenant are granted access And access without successful SSO (including existing sessions without SSO) is denied And upon sign-out or token expiration, subsequent requests are denied until re-authenticated And both web viewing and bundle download require an active SSO session
View and Download Limits Enforcement
Given a pack owner sets a view limit of V and a download limit of D on a link When viewers access and download content Then the system counts each successful view and download against the respective limit And upon reaching the limit, further attempts are blocked with a limit-exceeded message And partial loads or canceled downloads do not increment the counters And concurrent attempts are serialized so the limits cannot be exceeded And limits apply equally to web views and bundle downloads
Immediate One-Click Revocation
Given a pack owner clicks Revoke for a link When the revocation is confirmed Then the link becomes invalid immediately And any active sessions are terminated and further requests from those sessions are denied And both web viewing and bundle download endpoints return a revoked state without content And previously sent URLs cannot be reactivated, and regeneration creates a new signed link
Least-Privilege Scope by Team and Pack
Given a user is a member of Team T with access to Pack P When they create a share link Then the link grants access only to Pack P content and does not expose other packs or workspace data And only users with create-link permission in Team T can generate or manage links And optional restrictions by email domain or allowlist deny access to identities outside the configured scope And link metadata and audit views are only visible to authorized team members
Access Logging and Viewer Identity Watermark
Given a viewer attempts to access a link (view or download) When the request is processed Then an audit log entry is recorded with timestamp, IP, user agent, link ID, action (view/download), outcome (allowed/denied), and viewer identity where available And logs are immutable and filterable/exportable by time range and link ID And on successful access, the viewer’s identity (SSO name/email or provided identifier; otherwise "Anonymous") is watermarked on all rendered pages and in downloadable bundle artifacts And the watermark is visible yet non-obstructive and cannot be removed or disabled by viewers

BI Sync

Managed connectors and scheduled feeds to warehouses and BI tools with stable schemas, change logs, and a data dictionary for fairness metrics. Choose full or incremental syncs and map to cost centers and regions. Data teams get reliable pipelines—no manual CSV wrangling.

Requirements

Managed Warehouse & BI Connectors
"As a data engineer, I want to configure secure connectors to our warehouse and BI tools so that TimeTether data flows reliably without manual CSV handling."
Description

Provide secure, managed connections to data warehouses (Snowflake, BigQuery, Redshift, Databricks) and BI tools (Looker, Tableau, Power BI) using OAuth/service accounts with secrets stored in a vault. Support connection testing, health checks, credential rotation, minimal-permission scopes, and per-tenant isolation with consistent schema naming. Allow multiple destinations per tenant and dataset selection, enforce rate limits, and produce audit logs for all connection events. Integrate natively with TimeTether outputs to deliver fairness metrics and scheduling facts to chosen destinations without manual exports.

Acceptance Criteria
OAuth/Service Account Setup with Secrets Vault
Given a tenant admin opens the connector catalog listing Snowflake, BigQuery, Redshift, Databricks, Looker, Tableau, and Power BI When the admin configures any connector using OAuth or service account credentials Then the system stores all secrets in a managed vault, encrypted at rest, and not retrievable in plaintext via UI or API And only the connector runtime identity can access the decrypted secret at execution time And the system validates the credential by performing a non-mutating permissions check before saving And the connector is saved only if the vault write succeeds and the validation passes
Connection Test and Ongoing Health Checks
Given a configured connector with stored credentials When the user clicks Test Connection Then the system attempts a live connection and returns Success or Failure with a specific error code and redacted message And the connector Health status reflects the outcome immediately Given an active connector When periodic health checks run Then a failed check updates status to Unhealthy within the next check interval and records the failure reason And a subsequent successful check returns the status to Healthy
Credential Rotation Without Downtime
Given an active connector with scheduled syncs in progress When the admin rotates credentials (new secret or refreshed OAuth token) Then in-flight syncs complete using the old credential without failing And all new operations after save use the rotated credential And the previous credential is revoked/invalidated where supported by the destination And the rotation operation is atomic and retriable so that no partial state remains if a failure occurs
Minimal-Permission Scope Validation
Given connector setup for a specific destination When the user provides credentials Then the system verifies the credential’s roles/scopes are limited to the documented minimum required for read/write operations used by TimeTether And if excessive scopes/roles are detected, the system blocks saving and displays the minimal required scopes/roles to correct And a successful connection test must pass using only the minimal permissions
Per-Tenant Isolation and Consistent Schema Naming
Given Tenant A and Tenant B each configure a connector to the same warehouse destination When both tenants enable data delivery Then Tenant A’s data is written only under Tenant A’s credentials and namespace, and cannot be accessed by Tenant B’s connector identity, and vice versa And table and schema names follow a consistent pattern across destinations (e.g., timetether_<tenant_namespace>.fairness_metrics and timetether_<tenant_namespace>.scheduling_facts) adhering to destination naming rules And cross-tenant reads/writes are prevented by IAM/role isolation and network boundaries
Multiple Destinations and Dataset Selection with Native Delivery
Given a tenant with at least two configured connectors (e.g., Snowflake and Tableau) When the admin selects datasets per connector (fairness_metrics, scheduling_facts) Then only the selected datasets are delivered automatically to each destination without any manual exports or file uploads And changing dataset selection affects only future syncs for that connector and does not impact other connectors And the user can initiate an on-demand backfill for the selected datasets per connector
Rate Limit Enforcement and Comprehensive Audit Logging
Given any configured connector When syncs run and provider quotas apply Then the system enforces per-destination rate limits and honors 429/quota responses with exponential backoff and jitter until completion or max retry is reached And no connector exceeds configured request/concurrency thresholds as measured in execution logs And every connection event (create, update, delete, test, health check, credential rotation, sync start/finish/failure, rate-limit encounter) is recorded in an immutable audit log with timestamp, tenant, actor (user/system), destination, event type, and outcome And audit log records redact secrets and are queryable per tenant
Incremental and Full Sync Engine
"As a data engineer, I want to choose full or incremental sync with reliable resume and upserts so that pipelines are efficient and accurate."
Description

Enable selectable full refresh and incremental sync modes per table/subject area. Implement watermark-based and log-based CDC strategies with checkpointing, idempotent upsert/merge semantics, deduplication, and late-arriving data handling. Support initial backfill, partitioning by date/time, and resumable syncs after failure. Ensure consistency guarantees within the destination, validate row counts/hashes, and expose configuration for per-table keys, ordering, and conflict resolution.

Acceptance Criteria
Per-Table Sync Mode Selection
Given tables "meetings" and "participants" are configured with modes Incremental and Full respectively And "meetings" incremental strategy is watermark on column updated_at When a scheduled sync is triggered for source "prod" to destination "warehouse" Then the engine extracts only rows from "meetings" where updated_at > last_watermark and <= sync_start_time And the engine truncates and fully reloads "participants" And the run metadata records per-table mode, strategy, start_time, end_time, extracted_rows, loaded_rows, and status And the API returns HTTP 200 with per-table results matching the metadata
Watermark-Based Incremental with Checkpointing
Given last_watermark = 2025-08-30T00:00:00Z for table "meetings" And checkpointing is enabled with commit_interval = 10,000 rows When an incremental run processes 25,000 qualifying rows Then the destination contains exactly those 25,000 new/changed rows applied once And the stored last_watermark equals the max(updated_at) of committed rows And if the run is retried after failure at 18,000 rows, no row before the last committed checkpoint is re-applied and no eligible row is missed And rerunning the same successful batch produces zero net changes
Log-Based CDC Incremental Sync
Given log-based CDC is enabled for table "meetings" using source_offset = LSN 12345 And operations include 100 inserts, 30 updates, and 5 deletes When the incremental run executes Then changes are applied in log order to the destination using a transactional merge And the checkpoint is updated to the last processed LSN And a rerun from the saved checkpoint results in zero duplicate or missing changes And delete operations remove corresponding destination rows or mark them as deleted per configuration
Idempotent Upsert/Merge with Deduplication and Conflict Resolution
Given primary key = meeting_id and ordering column = updated_at with conflict_rule = "latest_timestamp_wins" And a batch contains duplicate keys with timestamps 10:01, 10:03, 10:02 for the same meeting_id When the batch is applied Then the destination has one row per meeting_id reflecting the 10:03 version And reapplying the identical batch produces zero changes And mixed insert/update events for the same key within one run resolve deterministically by ordering column And if ordering values tie, the configured tiebreaker (e.g., source_priority) is used and recorded in run metadata
Late-Arriving Data Handling
Given lateness_window = 72 hours and last_watermark = 2025-08-30T00:00:00Z And a record with updated_at = 2025-08-29T23:00:00Z arrives after watermark advancement When the correction pass executes Then the late record is ingested and applied to the destination And the system maintains a secondary watermark or backfill window to prevent reprocessing beyond the 72-hour window And records arriving older than lateness_window are written to a quarantine table with reason = "late_beyond_window" and are reported in the run summary
Initial Backfill and Partitioned Loads with Resumability
Given an initial backfill is configured for table "meetings" from 2023-01-01 to 2025-08-31 partitioned by month with max_concurrency = 4 When the backfill starts and a failure occurs after completing partitions Jan–Jul 2023 Then a retry resumes at Aug 2023 without reloading completed partitions And each partition is committed atomically; partial partitions are rolled back And per-partition row counts and hashes are recorded; any mismatch marks that partition as failed And overall backfill status is "Pass" only when all partitions pass validation
Consistency Validation and Reporting
Given validation is enabled with mode = "strict" When a full refresh completes for table "participants" Then destination row_count equals source row_count and a content hash matches exactly; otherwise the run status is "Fail" with diagnostics And for incremental runs, the number of applied changes equals the number of source change events processed, and sample hashes for affected partitions match And a run report is published via API and logged, containing per-table counts, hashes, checkpoint, duration, and anomalies
Stable Schema Contracts & Versioning
"As a BI developer, I want stable, versioned schemas with validations so that my dashboards do not break when fields change."
Description

Publish versioned, stable output schemas for all BI Sync datasets using semantic versioning. Enforce backward-compatible changes by default (additive columns), require explicit version bumps for breaking changes, and provide deprecation windows. Validate outputs against JSON Schema/DBT models during pipeline runs, enforce data types and constraints (PK/FK, uniqueness, not-null), and generate consumer notifications when schema changes occur. Guarantee fixed, human-readable naming conventions for fairness metrics and scheduling tables.

Acceptance Criteria
Enforce Semantic Versioning on BI Sync Schemas
Given an additive schema change (only new nullable columns or new non-referenced tables) When the change is proposed for release Then a minor version bump is required (X.Y.Z -> X.(Y+1).0) and the prior version remains available for at least 90 days And compatibility checks label the change as backward-compatible Given a breaking change (rename/remove column, data type narrowing, constraint tightening, PK/FK alterations) When the change is proposed Then a major version bump is required (X.Y.Z -> (X+1).0.0), publication is blocked until a deprecation window ≥ 90 days is configured, and migration notes are provided Given a documentation-only change with no contract impact When the change is proposed Then only a patch version bump is allowed (X.Y.Z -> X.Y.(Z+1))
Pipeline Schema Validation Gate (JSON Schema + DBT)
Given a BI Sync pipeline run (full or incremental) When validation executes prior to publish Then each output table validates against the versioned JSON Schema and DBT tests for data types, PK uniqueness, FK integrity, not-null, and enumerations And any single validation failure marks the run Failed and prevents publish And error output includes dataset_id, table, column, rule_id, and failing row samples (up to 20)
Human-Readable Naming Conventions Enforced
Given any fairness metric or scheduling table/column introduced or modified When the model is compiled in CI Then names must be lowercase snake_case, max 63 characters, no spaces, no leading numerics, and not in the reserved words list And required prefixes are enforced where applicable (fairness_, schedule_, timezone_) And acronyms must be expanded per the naming glossary And violations cause the build to fail with a linter report listing object, rule_id, and suggestion
Change Log and Consumer Notifications
Given any version bump (patch, minor, major) When the new version is published Then a change log entry is created with semver, effective_at, deprecated_at (if any), and a field-level schema diff (added/removed/changed) And notifications are dispatched within 5 minutes to all subscribed channels (email and webhook) including dataset_id, old_version, new_version, change_type, and a link to the data dictionary And webhook deliveries are retried with exponential backoff for 24 hours; failures are surfaced in an admin dashboard
Schema Contract Publication & Discovery API
Given a consumer requests a dataset contract When calling GET /contracts/{dataset_id}/versions/{semver} Then the API returns 200 with JSON including JSON Schema, DBT test manifest refs, PK/FK definitions, constraints, naming policy version, created_at, deprecated_at, and status (active|deprecated) And the endpoint supports ETag/If-None-Match and returns 304 when unchanged And the same versioned contract is visible in the data dictionary UI with matching metadata
Backward-Compatibility Test Matrix for Full vs Incremental
Given a change labeled backward-compatible When executed against both full and incremental syncs on a representative staging dataset Then no existing columns are removed or renamed, data types are unchanged, and constraints are not tightened And downstream snapshot comparisons report 0 breaking diffs relative to the previous released version And all tests must pass before the release pipeline can proceed
Fairness Metrics Data Dictionary & Catalog
"As a data analyst, I want an authoritative data dictionary for fairness metrics so that I can interpret and trust the data correctly."
Description

Auto-generate and publish a data dictionary covering all fields and metrics exposed via BI Sync, including definitions, calculation methodologies (e.g., after-hours rate, rotation fairness score), units, data types, and refresh cadence. Expose via API and UI with search, tags, owners, and PII flags, and export as YAML/JSON for catalog integration. Link entries to lineage and change logs to provide provenance and change history for each metric and field.

Acceptance Criteria
Auto-Generated Dictionary Coverage for Exposed Fields and Metrics
Given BI Sync exposes fields and metrics in the active schemas When the catalog generator runs after a successful sync or schema change event Then 100% of exposed fields and metrics have a dictionary entry created or updated within 15 minutes And each entry contains: id, name, type (field|metric), source_system, schema, table, column (nullable for derived metrics), description (>= 20 chars), data_type, unit (nullable), refresh_cadence, owner (email), tags[], is_pii (boolean, default false) And deprecated items are marked status='deprecated' with last_seen_at and remain searchable for at least 30 days
Documented Calculation Methodologies for Key Fairness Metrics
Given metrics "after_hours_rate" and "rotation_fairness_score" When the catalog is queried via API or UI Then each metric entry includes: plain-language definition, human-readable formula (SQL or pseudo-code), timezone handling rules, algorithm name and methodology_version, example calculation link, and stated assumptions And methodology_version follows SemVer (MAJOR.MINOR.PATCH) and is returned in API responses And when methodology_version changes Then a change log entry with reason, effective_at, and breaking_change flag is created and linked to the metric
Expose Data Types, Units, and Refresh Cadence via API and UI
Given a consumer views the catalog UI or calls GET /catalog/v1/entries When an entry is listed or fetched by id Then the response and UI display data_type, unit, and refresh_cadence with allowed values And refresh_cadence ∈ {hourly, daily, weekly, on_change}; data_type ∈ {string, integer, float, boolean, timestamp, date, array, object}; unit is a UCUM code or null And 95% of GET /catalog/v1/entries requests for a 10k-entry catalog return in ≤ 300 ms with pagination (page_size ≤ 100)
Search, Tags, Owners, and PII Flags Management
Given catalog entries have tags including cost_center and region and have assigned owners When a user calls GET /catalog/v1/search with q, tags[], owner, is_pii, limit, offset Then results match case-insensitive partials across name, description, and tags and include total_count And P95 latency ≤ 500 ms for a 10k-entry catalog And only roles {Admin, Data Steward} can modify is_pii via PATCH /catalog/v1/entries/{id}; others receive 403 and no change occurs And all PII flag changes are audit-logged with actor_id, timestamp, old_value, new_value And PII-flagged entries display a warning badge in UI and return pii=true in API
Deterministic YAML/JSON Export for Catalog Integration
Given an integration requests /catalog/v1/export?format=json or /catalog/v1/export?format=yaml When the export is generated Then the payload includes schema_version="1.0" and validates against JSON Schema catalog-1.0.json And keys are emitted in deterministic order and arrays are stable-sorted by name ascending And for catalogs > 10k entries or payload > 5 MB, the API streams results (chunked transfer or cursor pagination) and completes P95 in ≤ 10 s for 50k entries without 5xx errors And each exported entry includes lineage_ids[], change_log_ids[], and all required metadata from the dictionary
Lineage and Change History Linkage Per Entry
Given a consumer has an entry id for a field or metric When GET /catalog/v1/entries/{id} is called Then the response includes lineage.upstream[], lineage.downstream[], and latest_change_log {id, change_type, author, timestamp} And GET /catalog/v1/entries/{id}/history returns a reverse-chronological list of changes with diff summary and links to affected connectors/releases And lineage coverage ≥ 95% for entries mapped to sources; missing mappings set lineage.status='incomplete' And in the UI, selecting "View lineage" renders the graph within 2 seconds for entries with ≤ 200 nodes
Scheduler, Backfill, and SLA Alerts
"As a data platform owner, I want scheduled syncs with backfill and proactive alerts so that I can meet SLAs and quickly remediate failures."
Description

Provide a timezone-aware scheduler to run feeds on configurable intervals (hourly/daily/weekly) with dependency management, blackout windows, and pause/resume. Support range backfills with automatic partition planning, retries with exponential backoff, and a dead-letter queue for failed batches. Offer operational observability (run history, record counts, latency, error rates) and configurable SLAs with alerting via email, Slack, and webhooks. Enable one-click replay of failed runs and correlation IDs for cross-system tracing.

Acceptance Criteria
Timezone-Aware Interval Scheduling with Blackout Windows
Given a feed configured to run daily at 09:00 in America/Los_Angeles When a DST transition occurs Then the run fires at 09:00 local time without drift or duplication Given a blackout window from 18:00–07:00 local time When a scheduled time falls within the blackout Then the execution is deferred to the next permissible scheduled time outside the blackout Given the scheduler restarts between runs When it resumes Then it computes the next run from persisted state and does not re-run the last completed interval Given a feed has already executed for a given interval boundary When the scheduler evaluates due runs Then no more than one execution is created per interval boundary
Dependency-Aware Execution Ordering
Given Feed B declares a dependency on Feed A When Feed A completes with status Success for a scheduled interval Then Feed B is enqueued for that interval within 60 seconds Given Feed A fails or is skipped for an interval When evaluating Feed B for that interval Then Feed B is marked Blocked with reason and does not execute Given Feed B has multiple upstream dependencies When any upstream has not successfully completed the interval Then Feed B does not execute for that interval Given a circular dependency configuration is submitted When saving the configuration Then the system rejects it with a validation error indicating the cycle
Pause and Resume Feeds
Given a feed is set to Paused When its scheduled time arrives Then no execution starts and the run is recorded as Skipped (Paused) Given the feed is later Resumed When computing the next run Then the scheduler targets the next interval boundary and does not auto-run missed intervals Given dependent feeds exist When an upstream feed is Paused Then downstream runs for that interval are marked Blocked (Upstream Paused) and do not execute
Range Backfill with Automatic Partition Planning
Given a backfill request from 2025-07-01T00:00Z to 2025-07-07T00:00Z with daily partitioning When the backfill starts Then 6 daily partitions are created and processed oldest-first Given max_parallel_partitions is set to 3 When executing the backfill Then no more than 3 partitions run concurrently Given a partition has already succeeded previously When running a non-forced backfill Then the partition is skipped and recorded as Already Succeeded Given a backfill is canceled by a user When partitions are running Then in-flight partitions are gracefully stopped or marked Canceled and no new partitions are started
Retries with Exponential Backoff, Dead-Letter Queue, and One-Click Replay
Given retries are configured with max_attempts=5 and base_delay=60s and max_delay=600s When a batch fails with a retryable error Then retries are scheduled with exponential backoff (60s, 120s, 240s, 480s, 600s) until success or max_attempts reached Given a batch exceeds max_attempts or fails with a non-retryable error When handling the failure Then the batch is moved to the Dead-Letter Queue with metadata (timestamp, error class/message, attempt_count, partition_key, correlation_id) Given an item exists in the Dead-Letter Queue When a user triggers One-Click Replay Then a new run is enqueued within 60 seconds using the original parameters and correlation_id, and the DLQ item is linked to the replay outcome Given a replay and an original run target the same partition_key When both attempt to commit results Then exactly one commit is accepted and the other is deduplicated with a clear audit trail
Operational Observability: Run History, Metrics, and Tracing
Given a feed execution completes When persisting run data Then start_time, end_time, status, duration, input_count, output_count, error_count, and correlation_id are recorded Given the Run History API/UI is queried by feed_id, date range, status, or correlation_id When up to 10,000 records match Then results return within 2 seconds with correct pagination Given metrics emission is enabled When a run completes Then per-feed and per-partition metrics (latency, throughput, error rate) are available within 1 minute Given an error occurs in a run When viewing the run details Then the error message and stack trace (or error summary) are accessible for troubleshooting
Configurable SLAs and Multi-Channel Alerting
Given a freshness SLA of "complete within 2 hours of scheduled time" is configured When a run breaches the SLA Then alerts are delivered to Email, Slack, and configured Webhooks within 5 minutes including feed_id, run_id, severity, breach type, and deep links Given an availability SLA of "error rate < 1% over last 24h" is configured When the threshold is exceeded Then a single incident is opened and notifications are deduplicated for 30 minutes Given a webhook destination is configured with a shared secret When sending an alert Then the request includes an HMAC-SHA256 signature and timestamp headers Given blackout windows for alerts are configured When a breach occurs within a blackout window Then alerts are suppressed and a summary notification is sent when the blackout ends
Cost Center and Region Mapping
"As a finance analyst, I want meetings and metrics mapped to cost centers and regions so that I can allocate costs and analyze fairness by geography."
Description

Introduce configurable mapping from teams, users, and meetings to cost centers and regions using dimension tables and rules (domain/regex lookups, overrides). Enforce referential integrity and maintain history via SCD Type 2 to reflect organizational changes over time. Validate unmapped records, provide a review queue, and surface coverage metrics. Apply mappings during sync to propagate attributes into facts and aggregates, and support import/export of mappings via UI/API.

Acceptance Criteria
Mappings Applied During Sync Propagate to Facts and Aggregates
Given active mappings exist for teams, users, and meetings, and a scheduled BI Sync is triggered When the sync processes source records within the configured time window Then cost_center_id and region_id are populated on all facts (meetings_fact, invites_fact, participants_fact) using the mapping effective at the event timestamp (as_at) And downstream aggregates (meeting_burden_by_region, after_hours_by_cost_center) are computed using those attributes in the same run And records without a mapping retain null cost_center_id/region_id on facts and roll up under an "Unassigned" bucket in aggregates And the sync completion report includes counts: total_processed, mapped_count, unmapped_count per entity type
Rule-Based Mapping With Overrides and Precedence
Given the following rules exist: - domain rule: email_domain=acme.com -> cost_center=CC1 - regex rule: team_name matches ".*-EMEA" -> region=EMEA - override: user_id=alice -> cost_center=CC2 When evaluating alice@acme.com on team "Core-EMEA" Then cost_center_id=CC2 and region_id=EMEA due to precedence: override > team rule > domain rule > regex rule > default And an evaluation trace returns rule_ids applied in order with match fields and values When two rules of the same scope and attribute conflict without explicit precedence Then publishing the rule set is blocked with error code RULE_PRECEDENCE_REQUIRED and details of conflicting rules
Referential Integrity Enforcement on Fact Loads
Given cost_centers_dim(id, name, active) and regions_dim(id, name, active) exist When saving a mapping referencing a non-existent or inactive id Then the save is rejected with 400 MAP_INVALID_REF and the offending ids listed When loading facts with cost_center_id/region_id Then referential integrity is enforced; any violation causes the batch to fail atomically, writes a dead-letter record per offending row, and emits an alert And after correcting the dimension values, a retry of the same batch id succeeds without duplicate facts
SCD Type 2 History Preserved for Mapping Changes
Given a team-to-cost_center mapping (team_id=P1 -> CC1) current as of 2025-01-01 When the mapping is changed to CC2 effective 2025-06-15 Then the prior row is closed with valid_to=2025-06-14 23:59:59, current_flag=false, and a new row is inserted with valid_from=2025-06-15 00:00:00, valid_to=9999-12-31, current_flag=true, preserving the surrogate key lineage And queries with as_of_date=2025-06-01 resolve to CC1; as_of_date=2025-06-20 resolve to CC2 And the incremental sync publishes change log entries for both rows with scd_change_type="SCD2" and a stable schema version
Unmapped Records Validation and Review Workflow
Given a sync produces unmapped users, teams, or meetings When validation completes Then a review_queue entry is created per unique unmapped identifier with fields (entity_type, identifier, first_seen_at, last_seen_at, occurrence_count, suggested_rules[]) And an authorized admin can resolve an entry by creating a mapping or rule from the queue UI/API, which is applied on the next sync And coverage metrics are computed per entity type: coverage_pct = mapped_count / total_count, with a default threshold of 95% And if coverage_pct falls below threshold, an alert is sent to data_owners and the run status is "Completed with Warnings"
Mappings Import/Export via UI and API
Given a user with role=Data Admin requests an export Then a file is produced with schema_version, and rows containing (entity_scope, matcher_type, matcher_value, attribute_type, attribute_id, precedence, valid_from, valid_to, active), ordered deterministically, with SHA-256 checksum When importing via UI or API with dry_run=true Then the system validates schema, reports diffs (adds, updates, closes), and performs no writes When importing with dry_run=false Then upserts are idempotent, SCD2 rules are honored, and a summary is returned (inserted_count, updated_count, closed_count, skipped_count) And all import/export operations are audited (actor_id, timestamp, checksum, counts) and rate-limited to 10/minute; max file size 10 MB
Change Logs and Data Lineage
"As a governance lead, I want detailed change logs and lineage so that I can assess impact and maintain compliance across analytics consumers."
Description

Record and expose change logs for schema updates, metric definition adjustments, and pipeline releases, including before/after diffs and impacted datasets/dashboards. Visualize end-to-end lineage from TimeTether source events through transformations to destination tables. Provide an API/UI for querying changes by date/version, export OpenLineage-compatible events, and maintain an immutable audit trail with timestamps, actors, and references to release notes.

Acceptance Criteria
Schema Change Log Entry Creation
Given a schema change (add/remove/modify column properties) to any destination table in staging or production When the migration or transformation is applied and marked successful Then the system records a change log entry with change_id (UUID), environment, dataset.table, change_type, affected_columns[], before_definition (JSON), after_definition (JSON), impacted_datasets[], impacted_dashboards[], actor_id, actor_type, release_id, release_notes_url, version_before, version_after, timestamp_utc (ISO-8601), and checksum And the entry is available for query via API/UI within 60 seconds of completion And the entry is immutable; any update/delete attempts return HTTP 409 with error code AUDIT_IMMUTABLE
Metric Definition Adjustment Logging
Given a fairness metric definition (formula, filters, window, dimensions, thresholds) is created or updated When the change is saved Then the system records before_definition and after_definition JSON, metric_id, semantic_version change (major/minor/patch per breaking/non-breaking rule), effective_from timestamp, and links to impacted datasets/dashboards And downstream lineage is updated to reflect the new metric version within 5 minutes And re-computation jobs reference and display the exact metric version in lineage and logs
Pipeline Release Audit and Diff Recording
Given a pipeline release is deployed to staging or production When the release completes (success or failure) Then a release change log entry is recorded with release_id, commit_sha, artifact_version, environment, approver(s), status, started_at, completed_at, migration_scripts[] (names and hashes), aggregated before/after schema diff, and link to release notes And all schema change entries created during the release are linked by release_id And rollback releases create corresponding entries linked to the original release_id with reason and scope
Change Log Query via API and UI
Given a user or service queries changes by date or version When calling GET /api/v1/change-logs with filters (start, end, version, actor_id, resource_type, resource_id, environment, change_type) and pagination (limit<=500, cursor) Then the API returns 200 with results sorted by timestamp desc, a stable response schema, next_cursor if more, and completes within 800 ms for datasets up to 10,000 entries And the UI provides equivalent filters, renders first page within 2 seconds for up to 1,000 results, and supports export to CSV and JSON And timestamps display in user’s local timezone in UI while exports preserve UTC ISO-8601
End-to-End Data Lineage Visualization
Given a user opens the lineage view for a destination dataset/table When the view loads Then the graph displays nodes from TimeTether source events through transformations to destination datasets and linked dashboards with node metadata (id, type, name, version, last_run_at) and edge types (read/write/transform) And the graph renders in <=2 seconds for graphs with <=500 nodes and supports pan/zoom, search, and filtering by environment, date range, and cost center/region And selecting a node reveals incoming/outgoing dependencies and related change logs And the lineage graph can be exported as PNG and JSON
OpenLineage-Compatible Event Export
Given lineage or change events are produced and export is enabled When events are emitted Then the system outputs OpenLineage-compliant events (spec 1.0+) with job, run, and dataset facets (including schema and ownership) using correct namespaces/URNs And events are validated against the OpenLineage JSON schema; invalid events are rejected and logged with error code OL_VALIDATION_FAILED And events are delivered to the configured sink (Kafka topic, HTTP endpoint, or S3) with at-least-once semantics, up to 3 retries with exponential backoff (max 2 minutes), and dead-letter routing on final failure And each event includes correlation_id linking to change_id or release_id
Immutable Append-Only Audit Trail Enforcement
Given any attempt is made to modify or delete a recorded change log entry When a mutation request is received via API or UI Then the system denies mutation and writes an append-only correction record with correction_id, original_change_id, reason, actor_id, timestamp_utc, and references And daily integrity snapshots (Merkle root) are computed and stored; GET /api/v1/audit/proof returns integrity proof for a requested date range And WORM retention of 7 years is enforced; deletion attempts before expiry return HTTP 403 with error code AUDIT_RETENTION_ENFORCED

Product Ideas

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

Burden Budget

Set monthly after-hours credit budgets per team; TimeTether auto-debits unfair slots and blocks when exhausted. A live ledger shows who spends burdens.

Idea

Least-Privilege Locks

Use granular, time-boxed calendar OAuth with auto-expiring scopes and team-based policies. Reduce blast radius and simplify audits with per-scheduler access logs.

Idea

60-Second Spin-Up

Auto-import calendars, infer 90-day work windows, and prebuild fair rotations on connect. New users schedule a recurring series in under a minute.

Idea

Series-Smart Billing

Charge per active recurring series, not seats; pause billing when a series goes dormant. Finance gets predictable costs aligned to real usage.

Idea

Fatigue Forecast

Predict after-hours clusters for the next sprint using attendance histories and rotations; propose slot shifts before burnout hits.

Idea

One-Tap Nudge

When conflicts pop, push two ranked alternatives via Slack or Teams with one-tap RSVP. Meetings self-heal without email ping-pong.

Idea

Fairness Audit Trail

Export region-level after-hours rates, fairness scores, and policy breaches as CSV or BI feed. Equity teams prove improvements and flag hotspots.

Idea

Press Coverage

Imagined press coverage for this groundbreaking product concept.

P

TimeTether Launches AI Scheduling Assistant That Halves After‑Hours Meetings for Distributed Product and Engineering Teams

Imagined Press Article

San Francisco, CA — September 3, 2025 — TimeTether today announced general availability of its fairness‑driven scheduling assistant built for remote‑first product and engineering organizations. The platform automatically finds conflict‑free, recurring meeting times across time zones, applies a fairness rotation so late‑night and early‑morning slots are shared equitably, and dispatches one‑click invites. Early adopters report dramatic reductions in after‑hours meetings, faster time to schedule, and more predictable cadences for critical rituals like standups, planning, retros, 1:1s, release syncs, and KPI reviews. Summary: TimeTether analyzes real calendars and work windows, predicts humane scheduling windows, prebuilds fair rotations, and sends clean invites in seconds. It halves after‑hours meetings, cuts scheduling time, and ensures equitable meeting burdens across distributed teams. Built for the realities of distributed software delivery, TimeTether combines calendar intelligence with humane policy guardrails. After a one‑time connection to Google Workspace or Microsoft 365, One‑Click Import recognizes teams, attendees, and preferences, while Work Window Map infers each person’s 90‑day working hours, no‑meeting blocks, and timezone shifts from real activity patterns. Within the first minute, FairStart Rotation prebuilds a region‑balanced cadence for the next quarter, and RoleSmart Templates apply best‑practice defaults for common ceremonies. Instant Clash Check runs real‑time scans against calendars, focus blocks, and blackout periods to rank the top conflict‑free options by fairness score. With QuickSend Invites, coordinators launch a recurring series—complete with agenda snippets, conferencing links, and one‑tap RSVP—without email ping‑pong. “Distributed teams should not have to choose between velocity and wellbeing,” said Mira Chen, co‑founder and CEO of TimeTether. “TimeTether makes fairness the default. By understanding true work windows and rotating unavoidable burdens, we help teams cut after‑hours exposure while keeping their delivery rituals on schedule.” “Scheduling is rarely just picking an open slot. It is alignment, focus, and policy in one motion,” added Arun Sethi, Head of Product at TimeTether. “We designed TimeTether to collapse the entire workflow—discover the humane windows, balance regions and roles, prevent conflicts, and launch clean invites—in under a minute.” TimeTether’s design reflects the real personas inside modern software organizations. Ceremony Conductors (product managers) auto‑find windows across squads and time zones. Standup Captains (engineering leads) run daily follow‑the‑sun cadences without habitual after‑hours slots. 1:1 Balancers (people managers) batch and rotate recurring 1:1s within each report’s work window to cut late‑night calls and reschedule friction. Ops Harmonizers (program and delivery operations) prevent dependency clashes across teams and standardize rotations. Equity Guardians (People Ops and wellbeing leaders) set policy guardrails and audit after‑hours rates. Calendar Proxies (chiefs of staff and executive assistants) use one‑click proposals and auto‑constraints to protect focus blocks while keeping leaders’ recurring cadences intact. For organizations with rigorous security and compliance requirements, TimeTether includes enterprise‑grade access controls. JIT Grants provide just‑in‑time, task‑scoped calendar access that auto‑expires, while Scope Presets standardize least‑privilege scopes by role. Timebox Tokens enforce time‑boxed OAuth with hard stops, and Policy Simulator lets admins preview what a scheduler can do before granting access. Every touch is logged in the immutable Access Ledger, and Anomaly Alerts surface unusual access or cross‑team touches. Auto‑Revoke Sweep identifies and removes stale or orphaned connections nightly to minimize permission surface. TimeTether is equally deliberate about financial predictability. Organizations pay per active recurring series, not seats, enabled by Active Series Meter, Dormant Auto‑Pause, Smart Proration, Cost Center Tags, Series Line Items, Spend Guardrails, and Billing Forecast. Finance teams see exposure in real time, align charges to actual usage, and map spend directly to GL or BI systems. Results are measurable from day one. By learning from each interaction and honoring quiet hours, TimeTether reduces noise while increasing acceptance rates. Preference Memory adapts to individuals who prefer early starts or mid‑week slots, which means future proposals align better with lived patterns. Over time, teams see fewer declines, cleaner attendance, and a steady decrease in after‑hours occurrences. Availability and getting started: TimeTether is available today worldwide. New users can spin up in under a minute: connect your calendar suite, auto‑import the last 90 days of activity, review the inferred work windows, and accept a prebuilt, fairness‑driven rotation. From there, launch recurring series in seconds and monitor fairness and fatigue trends with Fatigue Radar, Smart Shift, Rotation Tuner, Burn Caps, Recovery Buffer, DST Shield, and the What‑If Sandbox. About TimeTether: TimeTether is a scheduling assistant purpose‑built for remote‑first product and engineering teams. By analyzing time zones and work windows, applying fairness‑driven rotations, and automating clean invites, TimeTether halves after‑hours meetings, cuts scheduling time, and distributes meeting burdens equitably across distributed teams. The platform includes robust access controls, predictive fatigue insights, and series‑smart billing for transparency and control. Media Contact Jordan Lee, PR and Communications press@timetether.com +1 (415) 555‑0199 www.timetether.com

P

TimeTether Introduces Meeting Equity Credits and Real‑Time Ledger to Bring Accountability and Fairness to After‑Hours Scheduling

Imagined Press Article

San Francisco, CA — September 3, 2025 — TimeTether today unveiled its Meeting Equity Credits and Ledger suite, a first‑of‑its‑kind set of budgeting, incentives, and transparent reporting capabilities that make humane scheduling measurable and manageable. The suite equips People Ops, finance leaders, and scheduling coordinators with clear baselines, proactive guardrails, and actionable insights to reduce after‑hours meetings and distribute unavoidable burdens equitably across regions and teams. Summary: The Meeting Equity Credits and Ledger suite introduces Smart Allocation, Rollover Flex, Credit Buyback, Series Caps, Threshold Alerts, Justified Overrides, and a Ledger Heatmap—paired with series‑smart billing—to align incentives with wellbeing and accountability, not just utilization. At launch, Smart Allocation auto‑issues monthly after‑hours credits per team based on team size, time zone spread, and historical demand. This ensures a fair baseline from day one and eliminates guesswork. Rollover Flex lets unused credits roll with configurable caps and expiries, smoothing quarter‑end crunches without sparking use‑it‑or‑lose‑it behavior. Credit Buyback rewards humane behavior by returning credits when owners move meetings into work hours, adopt fairness rotations, or cancel redundant sessions. Series Caps ensure a single ceremony cannot drain an entire team’s budget, while Threshold Alerts notify owners at 50, 75, 90, and 100 percent utilization with safe, suggested alternatives—such as slot swaps or temporary pauses—one click away. “When you can see, plan, and trade off fairness just as clearly as dollars, better behavior follows,” said Daniel Okafor, Chief Financial Officer at TimeTether. “Our Meeting Equity Credits align incentives with wellbeing, and our Ledger makes the tradeoffs obvious in real time. Finance gets predictability, managers get tools to reduce after‑hours friction, and teams get humane schedules.” “Equity must be visible to be actionable,” added Priya Menon, Director of Product for Equity and Governance at TimeTether. “The Ledger Heatmap exposes hotspots by region, team, and series. With Justified Overrides, leaders can approve exceptions when truly necessary—complete with brief logged rationales, approver workflows, and auto‑expiring exceptions—so agility and accountability coexist.” The suite integrates deeply with TimeTether’s series‑smart billing model. Active Series Meter shows exactly how many recurring series are billable at any moment. Dormant Auto‑Pause stops charges automatically when a series stops firing for its cadence‑defined threshold, then auto‑resumes on the next scheduled send. Smart Proration aligns charges precisely to usage when series start, stop, or change mid‑cycle and issues retro‑credits for early cancellations, so budgets reflect activity, not intentions. Cost Center Tags enable attendance‑weighted splits across projects or programs, while Series Line Items provide auditable invoice detail by cadence, active days, and adjustments. Spend Guardrails prevent new series from activating when caps would be exceeded, and Billing Forecast models quarter‑by‑quarter scenarios before anyone sends an invite. For Equity Guardians and program leaders, this means fewer surprises and cleaner conversations. The Equity Ledger logs region‑level after‑hours rates, fairness scores, and changes over time, while the Breach Register catalogs every policy breach and exception with severity, affected regions, and resolution status. Score Timeline adds trend analysis with event annotations (like daylight saving or headcount shifts), Region Benchmarks compare internal performance with industry percentiles, and Root‑Cause Trace ties each hotspot back to specific series, windows, and attendee cohorts. For audit‑heavy environments, Audit Pack assembles charts, extracts, and narrative context into a shareable, watermarked bundle, and BI Sync delivers managed connectors and scheduled feeds with stable schemas and a data dictionary for fairness metrics. “With TimeTether, we now manage meeting equity with the same rigor we apply to budgets and delivery,” said Mira Chen, co‑founder and CEO of TimeTether. “Leaders can prioritize wellbeing without sacrificing execution. The right behaviors become the easy default.” The Meeting Equity Credits and Ledger suite works hand‑in‑glove with TimeTether’s scheduling core and predictive fatigue features. Fatigue Radar surfaces next‑sprint after‑hours hotspots by person, team, and region, explaining the drivers behind risk. Smart Shift offers ranked slot moves to dissolve clusters with minimal disruption, while Rotation Tuner proactively rebalances upcoming rotations. Burn Caps set per‑person or team fatigue thresholds, and Recovery Buffer recommends humane recovery windows when intense stretches are forecasted. DST Shield anticipates daylight saving shifts that could turn normal meetings into after‑hours events and offers preemptive adjustments. Availability and next steps: The Meeting Equity Credits and Ledger suite is generally available today for all TimeTether customers on Business and Enterprise plans. Admins can enable credits and ledgers in the org settings, set caps and rollover policies, and map Cost Center Tags to accounting systems. Suggested best‑practice defaults are provided, and teams can adapt policies by region as maturity grows. About TimeTether: TimeTether is the scheduling assistant for remote‑first product and engineering teams. By analyzing time zones and work windows, applying fairness‑driven rotations, and automating clean invites, TimeTether halves after‑hours meetings, cuts scheduling time, and distributes meeting burdens equitably. Its enterprise features include access governance, predictive fatigue prevention, and series‑smart billing, with transparent equity metrics and audit‑ready exports. Media Contact Jordan Lee, PR and Communications press@timetether.com +1 (415) 555‑0199 www.timetether.com

P

TimeTether Unveils Self‑Healing Scheduling in Slack and Teams with Quorum Pick and Live Rerank

Imagined Press Article

San Francisco, CA — September 3, 2025 — TimeTether today launched a self‑healing scheduling experience that turns Slack and Microsoft Teams into fast, humane decision hubs for recurring meetings. The new capabilities—Quorum Pick, Nudge Guardrails, Live Rerank, Threaded Reminders, Preference Memory, and Guest Link—resolve conflicts in a single lightweight conversation and lock the best compliant slot the moment enough people respond. The result: fewer email threads, faster clarity, and materially lower after‑hours exposure for distributed teams. Summary: TimeTether’s in‑chat nudges present two ranked, policy‑compliant alternatives, tally responses in real time, and automatically confirm the winning slot once a quorum is reached. Every option is pre‑checked against fairness and fatigue policies, and externals can participate with a secure magic link—no login required. Quorum Pick allows coordinators to set a quorum based on required attendees or a percentage of invitees, along with a decision deadline. Once the quorum is met, TimeTether instantly confirms the winning slot, cancels conflicted occurrences, and sends updated invites—no babysitting needed. Live Rerank adjusts options dynamically as responses arrive, prioritizing choices by who has answered (required versus optional), fairness score, and predicted attendance. If a top choice becomes infeasible, the system quietly promotes the next best compliant slot, so the thread remains simple and up to date. “Self‑healing scheduling eliminates the two biggest sources of meeting fatigue: ambiguity and after‑hours drift,” said Mira Chen, co‑founder and CEO of TimeTether. “By making decisions in‑line and policy‑smart, teams get clarity faster and protect humane working hours—without the coordinator doing all the work.” Nudge Guardrails ensure every proposed alternative is compliant before it reaches participants. The system checks Burn Caps, Series Caps, Spend Guardrails, DST risks, and regional holidays to avoid after‑hours creep. If no fully compliant option exists, participants see a one‑tap, pre‑filled exception request that routes to approvers with the context they need to act quickly. Threaded Reminders keep all activity in one conversation, sending gentle nudges only to non‑responders and respecting quiet hours. Quick actions like “snooze,” “I’m flexible,” or “can’t make this time” reduce noise while increasing response rates. Preference Memory learns from each tap. Over time, it recognizes who consistently prefers earlier starts, mid‑week slots, or certain meeting lengths and folds those signals into future nudge windows without manual settings. The effect is cumulative: future options are more acceptable out of the gate, decline rates drop, and after‑hours exposure recedes. “Most tools stop at picking a time. The hard part starts when reality changes—people travel, priorities shift, and the calendar gets crowded,” said Devon Park, VP of Collaboration Experiences at TimeTether. “Our self‑healing approach closes the loop. Conflicts resolve themselves, fairness is preserved by design, and coordinators remain in control without micromanagement.” The self‑healing capabilities are tightly integrated with TimeTether’s predictive fatigue prevention. Fatigue Radar surfaces likely after‑hours clusters in the next sprint by person, team, and region. Smart Shift then offers ranked, low‑disruption slot moves; coordinators can accept a one‑click swap and TimeTether updates invites automatically. Rotation Tuner proactively rebalances upcoming rotations to spread after‑hours load before problems appear. Burn Caps set per‑person or team thresholds and block or warn when new bookings would exceed limits. Recovery Buffer suggests humane recovery windows and no‑meeting holds after intense stretches, and DST Shield anticipates daylight‑saving shifts that could flip humane meetings into after‑hours events. The What‑If Sandbox lets teams simulate changes to times, cadences, or attendees and instantly see how the fatigue forecast and fairness scores would change—then push decisions live in one click. For common personas across product and engineering, these capabilities translate into daily wins. Standup Captains running follow‑the‑sun cadences keep momentum without defaulting to the same region’s evenings. Ceremony Conductors coordinating planning and retros stop thread sprawl and lock humane slots quickly. 1:1 Balancers rotate demanding 1:1 loads fairly without manual shuffling. Partner Sync organizers bring external stakeholders into the same rapid flow through Guest Link, with secure mini pages that mirror the ranked choices and feed responses into the same tally and quorum rules. Security and governance remain first‑class. Every nudge thread respects organizational policies set by admins, including access scopes, quiet hours, and approver workflows for exceptions. All actions are recorded in the Access Ledger, with immutable who/what/when/why entries, and Anomaly Alerts flag unusual patterns like cross‑team touches outside policy. Auto‑Revoke Sweep cleans up stale connections nightly, and Policy Simulator helps admins preview outcome boundaries before enabling self‑healing at scale. Availability and getting started: Self‑healing scheduling is available today for all TimeTether customers with Slack or Microsoft Teams connected. Coordinators can enable Quorum Pick and Nudge Guardrails at the series level, configure default quorums and decision windows by meeting type, and allow Guest Link participation for approved external domains. About TimeTether: TimeTether is the fairness‑driven scheduling assistant for remote‑first product and engineering teams. It analyzes real calendars and work windows, applies equitable rotations, and dispatches clean invites in seconds—cutting after‑hours meetings and scheduling time while preserving execution cadence across time zones. With predictive fatigue prevention, access governance, and series‑smart billing, TimeTether turns humane scheduling into a measurable, accountable practice. Media Contact Jordan Lee, PR and Communications press@timetether.com +1 (415) 555‑0199 www.timetether.com

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.