Time zone scheduling software

Timeglue

Global teams, human hours

Timeglue is time zone scheduling software that finds sane cross-region meeting windows while honoring work hours, holidays, and focus blocks. Built for remote team leads at startups and agencies, it ends time zone math and back-and-forth: paint acceptable hours on a draggable timeline, share smart links, and prevent after-hours invites.

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

Timeglue

Product Details

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

Vision & Mission

Vision
Empower every global team to make time zones disappear, protect deep work, and reclaim hours for meaningful collaboration.
Long Term Goal
By 2029, help 50,000 global teams cut after-hours meetings by 50% and reclaim 10 million focused hours annually, boosting attendance 20% and slashing scheduling back-and-forth by 60%.
Impact
For remote team leads at startups and agencies, Timeglue cuts scheduling friction: 60% fewer messages, 35% faster time-to-meet, and 40% fewer after-hours meetings. Leads reclaim 3–5 hours weekly, while participants see 20% higher attendance and fewer reschedules across multi-region projects.

Problem & Solution

Problem Statement
Remote team leads at startups and agencies juggle global calendars, wasting hours proposing times and miscalculating time zones. Generic scheduling links ignore work hours, focus blocks, and local holidays, causing after-hours invites, meeting bloat, missed attendance, and morale hits.
Solution Overview
Timeglue automatically maps participants’ work hours, holidays, and focus blocks to propose sane cross‑region meeting windows. Hosts paint acceptable hours on a draggable timeline; smart invite links only show viable times per region, preventing after‑hours invites and ending back‑and‑forth timezone math.

Details & Audience

Description
Timeglue is time zone scheduling software that automatically finds sane meeting windows across regions while honoring work hours, holidays, and focus blocks. It serves remote team leads at startups and agencies who juggle global calendars. It ends timezone math and back-and-forth, protecting deep work and cutting after-hours meetings. A draggable timeline lets hosts paint acceptable hours and instantly generates smart invite links within those bounds.
Target Audience
Remote team leads (28-45) at startups and agencies battling timezone chaos, obsessively guarding focus blocks.
Inspiration
Thursday night, under the fridge light, our founder typed a quick sync to London and Sydney. Slack bloomed with polite apologies as the clock blinked 11:30 p.m. - another miscalculated window. She grabbed a pen at the cafe the next morning and drew a draggable timeline: paint human hours, respect focus blocks, let software do the math and share only sane slots. That sketch became Timeglue.

User Personas

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

C

Community Conductor Casey

- Age 30–38; open-source maintainer and part-time developer advocate. - Lives in Berlin; collaborators span North America, APAC, Africa. - Remote-first; core team 6 maintainers, 80+ contributors. - Tech stack GitHub, Slack, Zoom; income supplemented by sponsorships.

Background

Started as a contributor fixing docs during grad school; now leads weekly triage and monthly roadmap calls. Past burnout from late-night meetings drives insistence on humane scheduling and protected focus blocks.

Needs & Pain Points

Needs

1. Cluster triage calls within global humane overlap. 2. Auto-respect contributors’ local holidays and hours. 3. Share role-specific booking links per workstream.

Pain Points

1. Late-night triage draining volunteer motivation. 2. Confusing daylight savings wrecks recurring meetings. 3. Juggling ad-hoc contributors’ sporadic availability.

Psychographics

- Champions contributor well-being over velocity. - Obsessed with transparent, asynchronous-first collaboration. - Pragmatic; hates manual time zone math. - Values community rituals and predictable cadence.

Channels

1. GitHub Discussions — daily 2. Slack — project 3. Twitter/X — updates 4. Discord — community 5. Email — newsletter

R

Research Ringleader Riley

- Age 35–50; Principal Investigator or program manager. - Based in Toronto; collaborators in EU, India, Australia. - University setting; grants-funded; team 12–30 researchers. - Tools: Google Workspace, Zoom, Slack, ResearchGate.

Background

Former postdoc who lost grant time to calendar wrangling. A failed multi-site meeting due to daylight savings spurred tools that respect academic calendars and teaching blocks.

Needs & Pain Points

Needs

1. Encode teaching and lab schedules as hard no-go. 2. Batch cross-lab reviews into humane windows. 3. One link per committee with preset rules.

Pain Points

1. Clashes with teaching hours and lab experiments. 2. Semester breaks misaligned across institutions. 3. Last-minute Doodle polls waste precious time.

Psychographics

- Protects deep work and lab time fiercely. - Data-driven; favors reproducible, auditable processes. - Seeks fairness across time zones. - Minimizes administrative overhead with zeal.

Channels

1. Google Calendar — daily 2. Slack — department 3. Zoom — recurring 4. ResearchGate — updates 5. Email — faculty

W

Webinar Wrangler Wren

- Age 27–40; event or field marketing manager. - Based in Austin; audience and speakers globally. - B2B SaaS; team 3–8 marketers. - Tools: HubSpot, Zoom Webinar, LinkedIn Live, Google Calendar.

Background

Cut teeth running virtual summits during lockdowns, juggling 30+ speakers. A botched rehearsal at 2 a.m. cemented a no after-hours policy and reliance on guardrails.

Needs & Pain Points

Needs

1. Pre-build rehearsal windows per speaker region. 2. Lock booking inside business hours only. 3. One-click reschedule respecting all constraints.

Pain Points

1. Speakers booking outside agreed rehearsal windows. 2. Daylight savings shifting live event times. 3. Conflicts with executive calendars last minute.

Psychographics

- Fanatic about speaker experience and punctuality. - Operates with color-coded, checklist-driven precision. - Embraces automation; distrusts manual scheduling. - Cares about brand professionalism deeply.

Channels

1. LinkedIn — daily 2. Zoom — hosting 3. HubSpot — emails 4. Slack — team 5. YouTube — research

F

Freelance Fixer Felix

- Age 29–45; freelance PM/producer with multi-client portfolio. - Based in Lisbon; clients in US, UK, APAC. - Works from co-working spaces; income mid–high variability. - Tools: Notion, Trello, Google Workspace, Slack.

Background

Left agency life after calendar chaos and burnout. Built a boundary-driven practice where automation blocks scope creep, including meetings encroaching on evenings.

Needs & Pain Points

Needs

1. Client-specific smart links with guarded hours. 2. Auto-detect and exclude client holidays. 3. Batch recurring check-ins across clients.

Pain Points

1. Clients booking into protected focus blocks. 2. Conflicting holidays across regions cause cancellations. 3. Context-switching across calendars is exhausting.

Psychographics

- Time sovereignty is non-negotiable, always. - Optimizes for fewer, higher-quality meetings. - Values clear expectations and predictable routines. - Pragmatic; chooses tools that save minutes daily.

Channels

1. LinkedIn — prospecting 2. Upwork — sourcing 3. Slack — client 4. Email — primary 5. Notion — docs

L

Localization Liaison Luna

- Age 32–48; localization program manager. - Based in Singapore; stakeholders across EMEA, AMER, LATAM. - Company size 200–1,000; multi-product SaaS. - Tools: Smartling, Jira, Slack, Google Calendar.

Background

Started as a translator; promoted after rescuing a release derailed by mismatched time zones. A missed sign-off delayed a launch—now plans review windows ruthlessly.

Needs & Pain Points

Needs

1. Locale-based windows tied to release milestones. 2. Automatic holiday rules per country. 3. Role-specific links for translators vs reviewers.

Pain Points

1. Reviewers unavailable during critical freeze windows. 2. Vendor calls slipping into midnight hours. 3. National holidays breaking sprint cadence.

Psychographics

- Obsessed with on-time releases and handoffs. - Seeks harmony between vendors and internal teams. - Risk-averse; prefers guardrails over guidelines. - Data-minded; tracks cycle times religiously.

Channels

1. Slack — localization 2. Jira — workflows 3. Smartling — vendor 4. Email — approvals 5. LinkedIn — community

N

NGO Nexus Nia

- Age 26–38; program coordinator/manager. - Based in Nairobi; partners across MENA, South Asia, LATAM. - Organization size 20–150; hybrid/remote. - Tools: WhatsApp, Zoom, Google Workspace, Airtable.

Background

Learned coordination during disaster response deployments where timing meant impact. After repeated after-hours calls strained partner relationships, adopted strict scheduling norms.

Needs & Pain Points

Needs

1. Schedule trainings within mutually humane overlap. 2. Respect religious and national holidays automatically. 3. Offline-friendly invites and reminders.

Pain Points

1. Partners refuse late-night or weekend meetings. 2. Unstable connectivity requires fewer reschedules. 3. Holiday mismatches derail donor check-ins.

Psychographics

- Empathy-led; prioritizes partner and community well-being. - Seeks equitable participation across regions. - Frugal; favors value over flash. - Mission-driven, outcomes-focused, accountable to stakeholders.

Channels

1. WhatsApp — primary 2. Email — formal 3. Zoom — sessions 4. LinkedIn — networking 5. Google Calendar — invites

Product Features

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

Role Weights

Assign per-role, seniority, or stakeholder weights so rotations distribute outside-normal-hour burden equitably—not just equally. Give candidates, customers, or protected roles lighter weights while keeping transparency with a team-visible rationale. FairShare uses the weights to pick next slots that stay within work-hour guardrails and preserve overall balance.

Requirements

Weight Model & Normalization Engine
"As a rotation owner, I want to define weighted participation rules across roles and seniority so that after-hours burden is distributed fairly and predictably."
Description

Introduce a configurable weighting system that assigns relative participation coefficients to roles, seniority bands, stakeholder types, teams, and individual overrides. Weights default to 1.0 and can range from 0.1 to 2.0, with validation to prevent zero or negative totals within a rotation pool. Effective weights are normalized per rotation pool and computed after applying exemptions (e.g., protected roles, candidates, customers), local holiday calendars, work-hour guardrails, focus blocks, and PTO. Support versioned weight profiles with effective-dating, fallback rules when metadata is missing, and caps on maximum after-hours assignments per user per period. Integrate with Timeglue’s org directory and role/seniority mappings, ensure idempotent updates, and provide deterministic calculations resilient to partial data. The outcome is a consistent, equitable baseline that downstream scheduling can rely on to distribute outside-normal-hour burden fairly while respecting hard constraints.

Acceptance Criteria
Weight Input Validation & Defaults
- Given a rotation pool with defined participants, When saving a weight profile, Then any omitted weight fields default to 1.0. - Given any weight value outside the allowed range 0.1–2.0, When validation runs, Then the save is blocked and a validation error specifies the offending field(s) and allowed range. - Given any individual weight set to 0 or negative, When validation runs, Then the save is blocked with a non-positive weight error. - Given the current pool after applying exemptions has a total effective weight ≤ 0, When attempting to activate the profile, Then activation is blocked with an error indicating no eligible participants and listing the applicable exemptions.
Constraint-Aware Normalization per Rotation Run
- Given a scheduling window and a rotation pool, When computing effective weights, Then participants unavailable due to work-hour guardrails, local holidays, PTO, focus blocks, or defined exemptions receive an effective weight of 0 for that window. - Given the remaining eligible participants and their configured weights, When normalization completes, Then the sum of normalized weights equals 1.0 within a tolerance of 1e-6. - Given a participant is available for only a subset of the window, When computing per candidate timeslot, Then availability is evaluated per timeslot and weights are normalized over only eligible participants for that timeslot. - Given identical inputs, When normalization is recomputed multiple times, Then the resulting normalized weights and ordering are identical.
Versioned Profiles & Effective Dating with Fallback
- Given multiple weight profiles with effective start and end timestamps, When computing weights for a timestamp, Then the profile whose window covers the timestamp is used. - Given no active profile covers the timestamp, When computing weights, Then the most recent prior profile is used; if none exists, default weights of 1.0 are applied. - Given a profile has passed its end timestamp, When attempting to modify it, Then the system prevents edits and requires creating a new version. - Given a scheduling range that spans multiple profile versions, When computing per timeslot, Then the engine uses the correct version per timeslot without mixing coefficients across versions.
Org Directory Mapping & Missing Metadata Resolution
- Given a user with an individual override weight, When resolving effective base weight, Then the individual override takes precedence over all other category weights. - Given no individual override, When resolving weight, Then the engine checks weights in the following precedence order and uses the first defined: role → seniority band → stakeholder type → team → default 1.0. - Given required mapping data is missing from the org directory, When resolving weight, Then the system applies the fallback precedence and still produces a deterministic weight. - Given the org directory updates a user’s role/seniority/stakeholder/team, When the weight resolution is recomputed with unchanged profile values, Then the new mapping is reflected without manual resave of the profile.
Idempotent Updates and Deterministic Output Under Partial Data
- Given a weight profile is saved twice with identical payloads, When inspecting the resulting profile version and hash, Then only one effective version exists and the computed weights are unchanged. - Given transient failures in fetching non-critical metadata (e.g., holiday calendars) occur, When the engine recomputes after retry with the same final inputs, Then the normalized output matches prior results. - Given ties in normalized weights, When producing the ordered candidate list, Then a stable, documented tiebreaker (e.g., user UUID ascending) is applied consistently across runs. - Given an identical input set is provided across different nodes, When computations complete, Then all nodes produce byte-identical normalized outputs.
After-Hours Caps Per Period
- Given a per-user cap configured as max K after-hours assignments in a rolling period P, When a user reaches the cap, Then their effective weight for after-hours windows becomes 0 until they drop below the cap. - Given at least one eligible participant remains under cap, When normalization runs for an after-hours window, Then weights are redistributed across only under-cap participants and sum to 1.0 within tolerance. - Given all participants are at or above cap for an after-hours window, When normalization runs, Then the engine returns a no eligible participants under cap state rather than violating the cap. - Given a user falls back below the cap as the window rolls forward, When normalization runs for subsequent windows, Then the user is reinstated with their configured weight considered in normalization.
Role Weight Assignment UI with Rationale
"As a team lead, I want an intuitive UI to set and justify role weights so that the team understands and trusts how fairness is determined."
Description

Provide an admin-facing interface to create, view, and edit weights using sliders and numeric inputs, with presets and templates per role/seniority/stakeholder. Display calculated effective weight, normalization impacts, and constraint warnings (e.g., exceeds caps or violates guardrails) in real time. Require a short, team-visible rationale note on any non-default weight, and surface these rationales in team views for transparency. Support bulk-edit, search/filter, undo/redo, draft versus published changes, and change previews showing projected burden shifts before saving. Place the UI under Team Settings > Rotations and make it responsive, accessible (WCAG AA), and keyboard-navigable.

Acceptance Criteria
Assigning and editing role weights via slider and numeric input with presets/templates
Given an admin on Team Settings > Rotations > Role Weights When a role/seniority/stakeholder is selected Then a weight control is visible as both a slider and a numeric input synchronized to at least one decimal place with a default value When the slider is adjusted Then the numeric input updates to the same value within 300ms When a valid numeric value is entered Then the slider position updates accordingly and invalid entries show inline validation and disable save When a preset or template is applied Then the weight value updates and the applied preset/template name is shown on the form
Real-time effective weight, normalization, and constraint warnings
Given one or more weight values are modified When any change is made Then the effective weight after normalization is displayed per affected entity with a delta from the last published value And any constraint issue (e.g., exceeds cap, violates guardrails) displays a real-time warning that names the specific constraint and entity And warnings clear automatically when the issue is resolved And all indicators update within 300ms of user input
Rationale requirement for non-default weights and team-visible transparency
Given a weight deviates from its default or preset value When attempting to Save Draft or Publish Then a rationale text field (10–200 characters) is required for each changed weight, and actions are disabled until all required rationales are provided When a change is published Then the rationale is visible in team-facing views alongside the corresponding weight or via an info tooltip
Bulk-edit, search/filter, undo/redo, and draft vs published workflow
Given multiple roles are selected When Bulk Edit is applied with a numeric value or preset Then the change is applied to all selected and a single rationale can be applied to all affected non-default weights Given the list of roles When using search and filters Then results can be narrowed by free-text, role, seniority, stakeholder, weight range, and presence/absence of rationale Given a sequence of edits in the current session When Undo or Redo is used Then changes are reverted/applied stepwise for at least the last 20 actions, including bulk operations and rationales Given edits exist When Save Draft is used Then scheduling is unaffected and a Draft state is shown with last-saved timestamp When Publish is used Then the new configuration replaces the published state and Draft indicators are cleared
Change preview showing projected burden shifts before saving
Given draft weight changes exist When Preview Changes is opened Then a projected burden distribution for the next rotation interval is shown with before/after values and net change per person/role, including outside-normal-hours share And the preview respects work-hour guardrails and holidays And the projection matches backend FairShare calculations within 0.5% per entity And the preview renders within 2 seconds for teams up to 100 members
Navigation placement, accessibility (WCAG AA), and keyboard operability
Given a user with admin permissions When navigating to Team Settings > Rotations Then a Role Weights section is present and loads without error And all interactive controls are operable with keyboard only (Tab/Shift+Tab for focus, Arrow keys for sliders) with visible focus indicators And sliders and inputs have accessible names/values and announce changes via screen readers And color contrast, focus order, form validation messaging, and live region announcements conform to WCAG 2.1 AA And automated accessibility testing (e.g., axe-core) reports no serious or critical violations on desktop and mobile views
Responsive layout and performance
Given the Role Weights UI is viewed on different devices When rendered at 320px, 768px, and 1280px widths Then the layout adapts without horizontal scrolling, controls remain usable, and touch targets are at least 44x44px on touch devices And initial load reaches interactive within 1.0s on desktop and 2.0s on mid-tier mobile for 50 roles over a 3G Fast network And resizing the viewport does not cause layout breakage or loss of entered data
FairShare Scheduler Integration
"As a scheduler, I want FairShare to apply role weights within guardrails so that assignments stay humane while maintaining overall balance over time."
Description

Enhance the FairShare scheduling algorithm to consume effective weights and produce rotation assignments that honor work-hour guardrails, holidays, and focus blocks while preserving long-term balance. Implement drift detection and correction to steer actual burden toward weighted targets over time, with tie-breakers (e.g., least recent assignment, lowest after-hours count) for deterministic outcomes. Support backfill logic when a candidate is unavailable, and maintain performance for large pools (O(n log n) target) with concurrency-safe operations. Emit trace data explaining assignment decisions to enable user-facing transparency and auditability.

Acceptance Criteria
Weighted Scheduling within Guardrails
Given a roster of 20 participants with per-user weights, defined available windows (work hours), labeled outside-normal-hour windows, and blocked periods (holidays, focus blocks) When the scheduler generates 1000 assignments over a 30-day horizon Then 100% of assignments fall within each assignee’s available windows and do not overlap any blocked period And each participant’s total assignment share is within ±2 percentage points of their weighted target share across the 1000 assignments And each participant’s outside-normal-hour assignment share is within ±2 percentage points of their weighted target outside-hours share And no participant exceeds the configured per-week assignment cap
Drift Detection and Correction Over Time
Given historical assignment data shows a participant’s outside-normal-hour burden deviates by +10 percentage points above their weighted target over the last 90 days When the scheduler runs for the next 200 assignments Then drift is detected and recorded with participantId and deviation amount in telemetry And subsequent assignments reduce deviation to ≤2 percentage points within those 200 assignments without violating guardrails And no single week exceeds the configured max-catchup rate during correction
Deterministic Tie-Breaker Ordering
Given two or more eligible candidates are tied on the weight-adjusted score for a slot When the scheduler selects an assignee Then it applies tie-breakers in this order: (1) least recent assignment, (2) lowest outside-normal-hours count in last 30 days, (3) lowest within-hours count in last 30 days, (4) lowest userId lexicographically And 100 repeated runs with identical inputs (including seed) produce the same assignee and the same recorded tie-breaker path
Backfill and Unavailability Handling
Given the initially selected assignee declines or is marked unavailable for a slot When the scheduler attempts backfill Then it selects the next eligible candidate using the same scoring and tie-breaker rules within 200 ms And if no eligible candidate exists, it emits a no-assignable-candidate event and leaves the slot unassigned after at most 3 attempts And backfill updates drift metrics consistently so long-term balance remains within ±2 percentage points of targets over the next 200 assignments
Performance and Concurrency at Scale
Given a pool of n=10,000 participants with random availability and weights on the defined baseline hardware When scheduling 1,000 independent slots Then average assignment latency ≤ 200 ms and p95 ≤ 500 ms And empirical complexity exhibits doubling ratio consistent with O(n log n) (≤ 1.2× predicted baseline when n doubles) And with 100 parallel scheduling requests over overlapping pools, no duplicate assignments occur and all writes are atomic And retries with the same requestId return the original result without creating new assignments (idempotent)
Traceable and Auditable Decision Output
Given trace emission is enabled When an assignment (including any backfill) is produced Then a trace record is persisted with correlationId containing: candidate set before filters, filters applied with counts removed, effective weights, per-candidate scores, tie-breaker evaluations, drift inputs and corrections, and the final selection And the trace is retrievable via API within 1 second of assignment and retained for at least 90 days And trace payload excludes PII beyond userId and roleId And rendering the human-readable explanation from the trace reproduces the same assignee as the engine for the same inputs
Transparency Dashboard & Reports
"As a team lead, I want reports showing how actual burden compares to weighted targets so that I can prove fairness and quickly correct drift."
Description

Deliver a dashboard that visualizes target versus actual burden distribution by person, role, team, and time window, including after-hours counts, deviation percentages, and trend lines. Provide drill-downs to individual assignment histories with explanation traces (“why you were selected”) sourced from the scheduler. Offer configurable alerts when deviation from target weights exceeds thresholds, and exportable reports (CSV/PDF) for leadership and clients. Ensure data retention aligns with org policy and supports comparison across time zones and holiday calendars.

Acceptance Criteria
Dashboard shows target vs actual burden by person/role/team/time window
Given an organization with role weights, work-hour definitions, holidays, and at least 90 days of scheduling history When a user with permission opens the Transparency Dashboard and selects a time window (e.g., last 90 days) and a dimension (person, role, team) Then the dashboard displays target vs actual burden percentages for the selected dimension and time window And shows deviation (actual − target) in percentage points for each entity with color-coded thresholds And shows after-hours counts computed using each assignee’s local work hours, focus blocks, and holidays And renders trend lines (daily/weekly) for deviation over the selected window And totals and averages at the bottom reflect the filtered set and match exported figures And empty states appear with guidance when no data matches filters (no NaN/Infinity displayed)
Drill-down to assignment history with rationale trace
Given a dashboard card for a person/role/team is visible When the user clicks “View details” Then a drill-down opens listing all relevant assignments within the selected window with timestamps in the assignee’s local timezone And each assignment row includes an explanation trace showing: role weight at selection time, candidate pool, guardrails applied (work hours, holidays, focus blocks), and why alternatives were skipped And the trace includes a scheduler decision ID/hash linking back to audit logs And pagination, sorting, and filtering (by after-hours vs in-hours) are available and performant (page loads ≤2s at P95 for up to 10k rows)
Configurable deviation alerts and notifications
Given an admin configures deviation thresholds per dimension (person/role/team) with a comparator (e.g., > 5 percentage points) and a rolling window (e.g., 30 days) When actual deviation exceeds the configured threshold at the next evaluation cycle Then an alert is generated within 5 minutes and delivered via the selected channels (in-app banner, email) And alert payload includes entity, window, target, actual, deviation, and a link to the drill-down And alerting implements suppression: max one alert per entity per hour until resolved and auto-resolves when deviation returns within threshold for 24h And all alerts are logged with timestamp, recipient, status (sent/failed), and retry policy (up to 3 retries over 10 minutes)
Exportable reports (CSV/PDF) mirror on-screen filters
Given the dashboard is filtered to a specific time window, dimension, and set of entities When the user exports CSV or PDF Then the exported data matches the on-screen aggregates, after-hours counts, deviations, and trend summaries 1:1 And the file includes metadata: generated_at (ISO 8601 with timezone), filters, dimension, org ID, and app version And CSV uses UTF-8 with headers; PDF is paginated, legible, and includes charts with accessible alt text And exports complete within 30 seconds for up to 100k rows; larger requests prompt asynchronous export with email delivery And redaction options are available for client-facing exports (remove PII, show roles/team aggregates only)
Data retention, privacy, and cross-timezone/holiday correctness
Given an org-configured retention policy (e.g., 180/365/730 days) and legal hold exceptions When data exceeds the retention window without legal hold Then assignment details and personal identifiers are purged/redacted while retained aggregates remain correct And comparisons normalize timestamps to the assignee’s local timezone and correctly exclude organization-defined holidays and focus blocks from “work hours” And dashboards and exports clearly indicate the calendars/timezones used And audit logs record purges/redactions with timestamps and scope And attempts to access purged drill-down data return a non-sensitive placeholder without errors
Performance, accuracy, and data integrity safeguards
Given typical org size up to 5k users and 100k assignments in a 12-month window When the dashboard loads, filters change, or drill-downs paginate Then P95 render/response time is ≤3 seconds and P99 ≤6 seconds And deviation percentages are computed using the documented formula and rounded to one decimal place; totals reconcile (sum of actual burden ≈ 100% within ±0.1%) And after-hours counts are non-negative and equal the number of assignments outside configured work hours And data anomalies (missing weights, invalid calendars) surface as inline warnings and are excluded from aggregates with counts of excluded records
Access control and transparency boundaries
Given role-based permissions are configured When a user without “View Transparency (person-level)” permission accesses the dashboard Then they see only role/team-level aggregates and redacted drill-downs And users with the permission can view person-level data and explanation traces And protected roles marked as sensitive display rationale without exposing PII beyond authorized scopes And all access is audited with user ID, timestamp, resource, and outcome (allowed/denied)
Weight Management API & Import/Export
"As a platform engineer, I want APIs and bulk import/export for weights so that we can automate updates and integrate with our HRIS at scale."
Description

Expose secure REST/GraphQL endpoints to create, read, update, and delete weights at role, team, and user levels, with bulk operations, optimistic concurrency, and granular scopes. Provide CSV import/export for quick edits and a mapping layer to sync role/seniority from HRIS/SCIM, including idempotent upserts and validation feedback. Publish webhooks for weight and policy changes so downstream systems and audits remain current. Enforce rate limits and input validation to maintain integrity across large organizations.

Acceptance Criteria
Scoped CRUD Endpoints for Role/Team/User Weights
Given a valid OAuth2 access token with scope "weights:read", when the client GETs "/v1/weights?level=role", then the response is 200 and contains only resources within the token's org/team scope, each with fields ["id","level","targetId","weight","rationale","updatedAt","etag"]. Given a token without "weights:write", when the client POSTs "/v1/weights" with any body, then the response is 403. Given scope "weights:write", when the client POSTs "/v1/weights" with body {"level":"user","targetId":"user_123","weight":0.5,"rationale":"Candidate"}, then the response is 201 with "Location" and "ETag" headers and the persisted resource matches the body. Given an invalid payload (e.g., missing "level" or non-numeric "weight"), when the client POSTs "/v1/weights", then the response is 422 with machine-readable error details per field. Given a GraphQL query "{ weights(level: TEAM, teamId: \"team_1\") { nodes { id level targetId weight rationale updatedAt } } }", when executed with scope "weights:read", then the response contains only permitted nodes.
Optimistic Concurrency Control on Updates
Given a weight resource with ETag "abc123", when the client PATCHes "/v1/weights/{id}" with header "If-Match: abc123" and a valid body, then the response is 200, the resource is updated exactly once, and a new ETag is returned. Given the client PATCHes or PUTs "/v1/weights/{id}" with a stale "If-Match" value, then the response is 412 Precondition Failed and no changes are persisted. Given the client PATCHes or PUTs "/v1/weights/{id}" without an "If-Match" header, then the response is 428 Precondition Required. Given two concurrent update attempts with different bodies, then at most one succeeds; the loser receives 412 and the final persisted state matches the winner's payload.
Bulk Operations with Partial Success Reporting
Given a POST to "/v1/weights/bulk" with a JSON array of up to 5000 items, then the API responds 202 Accepted with a jobId and processing begins. Given a GET to "/v1/jobs/{jobId}", when processing completes (≤ 15s p95 for 5000 items), then the result includes per-item outcomes: created, updated, skipped, failed. Given mixed validity in the bulk payload, then successful items are committed and failed items are not; the job result includes per-item error codes and messages; the operation is atomic per item. Given the same bulk payload is submitted twice, then the second submission performs idempotent upserts and reports items as skipped or updated with no duplicates created.
CSV Import/Export with Idempotent Upserts and Validation Feedback
Given a GET to "/v1/weights/export?level=user&teamId=team_1", then the response is 200 with Content-Type "text/csv" (RFC 4180), includes a header row [level,targetId,teamId,roleId,userId,weight,rationale,source,externalId], and rows reflect current data within scope. Given a POST to "/v1/weights/import?dryRun=true" with a CSV file ≤ 25MB and ≤ 10000 rows, then the response is 202 with a jobId; the job completes with a detailed validation report and no data changes. Given a POST to "/v1/weights/import" with valid rows, then each row performs an idempotent upsert keyed by externalId if present, else by (level,targetId); duplicates within the file are resolved by last-wins and reported as warnings. Given invalid rows (e.g., unknown teamId, non-numeric weight, missing targetId), then no changes are applied for those rows; the job result lists row number, field, error code, and message; valid rows in the same file are still applied.
HRIS/SCIM Mapping Sync for Role/Seniority
Given a configured mapping that translates HRIS/SCIM attributes (e.g., titleBand, department, isStakeholder) to Timeglue weight rules, when the client POSTs "/v1/sync/hris" with user records that include "externalId" and mapped attributes, then the system applies idempotent upserts for matching users within scope. Given the same HRIS payload is sent repeatedly, then subsequent syncs result in zero duplicate resources and only differing values are updated; the response summarizes counts for matched, created, updated, skipped, and failed. Given a record contains an unmapped or invalid attribute value, then that record is skipped with a per-record error code (e.g., "mapping_not_found") and no partial changes are applied to it. Given SCIM delta updates (PATCH) arrive while a full sync is in progress, then they are queued or processed safely without data loss and preserve overall idempotency.
Webhooks for Weight and Policy Changes
Given webhooks are configured with a secret, when a weight or policy is created, updated, or deleted, then an event of type one of ["weight.created","weight.updated","weight.deleted","policy.updated"] is delivered within 30s p99 of commit. Given an event is delivered, then the HTTP request includes headers "X-Timeglue-Signature" (HMAC-SHA256 over the body with timestamp) and "Idempotency-Key"; the payload includes eventId, occurredAt, actorId, resource before/after values, and orgId. Given the receiver responds non-2xx, then the system retries up to 3 times with exponential backoff (1m, 5m, 20m); after final failure the event is marked failed and visible in delivery logs; no duplicate processing occurs if the receiver deduplicates on eventId or Idempotency-Key. Given a test/ping action is triggered, then a "webhook.test" event is sent to validate configuration without changing data.
Rate Limiting and Input Validation Enforcement
Given normal API usage, then requests are limited to 600 requests/minute per access token; when the limit is exceeded, responses are 429 with headers "X-RateLimit-Limit","X-RateLimit-Remaining","X-RateLimit-Reset" and a "Retry-After" value. Given a single-object request with a body exceeding 1MB, then the response is 413 Payload Too Large and the body is not processed; for CSV import the maximum is 25MB; for JSON bulk the maximum is 5000 items. Given a weight value, then it must be a decimal between 0.10 and 10.00 inclusive with up to 2 decimal places; invalid values result in 422 with error code "weight_out_of_range". Given an attempt to create a second active weight for the same (level,targetId,scope) without explicit upsert semantics, then the response is 409 Conflict; with "upsert=true", the existing record is updated instead and 200 is returned.
Access Control & Governance
"As an admin, I want governed, auditable weight changes with approval and rationale so that fairness policies remain controlled and compliant."
Description

Implement role-based permissions that restrict who can view or modify weights, with configurable approvers for sensitive changes (e.g., protected roles). Require mandatory rationale notes for non-default changes, capture full change history (who, what, when, why), and support effective-dated changes with scheduled activation and rollback. Notify impacted teams on publishing, and provide read-only visibility of rationales to all rotation members to maintain transparency. Align logs with compliance retention policies and provide export for audits.

Acceptance Criteria
RBAC: View and Modify Weights
Given a user lacks Weight.Viewer for Team A When they request weights for Team A via UI or API Then the request is denied with 403 and no weight values are disclosed Given a user has Weight.Viewer for Team A When they request weights for Team A Then the system returns the current weights limited to Team A scope Given a user lacks Weight.Editor for Team A When they attempt to create, update, or delete a weight in Team A Then the action is blocked with 403 and an audit entry is recorded Given a user has Weight.Editor for Team A When they submit a valid weight change Then a draft change is created and routed to approval if required by policy
Configurable Approvals for Protected Roles
Given a weight change targets a role marked Protected in Team A and the approval policy is "2 approvers: any Admin + HR" When a requester submits the change Then the change status becomes Pending Approval and cannot be published until two distinct approvers approve Given the requester is included in the approver policy When they attempt to approve their own request Then the approval is rejected with 403 and reason "Self-approval not allowed" Given a pending approval exceeds the configured SLA (e.g., 3 business days) When the SLA elapses Then the request auto-expires to Rejected: SLA Expired and requester/approvers are notified Given an approver rejects with a reason When the rejection is submitted Then the request is marked Rejected, notifications are sent, and the rejection reason is captured in the audit log
Mandatory Rationale for Non-Default Changes
Given a submitted change sets weight to a value not equal to 1.0 or targets a Protected role When the requester omits a rationale Then the submission is rejected with validation error "Rationale required" and is not saved Given a rationale is provided with 15–1000 characters and a category selected from the configured set When the change is submitted Then validation passes and the rationale is stored and linked to the change Given a change reverts a non-protected role to the default weight of 1.0 When the requester omits a rationale Then the submission is accepted and recorded with rationale "Not provided (default revert)"
Audit Trail, Retention, and Export
Given any weight change is created, updated, approved, activated, rolled back, or rejected When the event occurs Then an immutable audit record is written with fields: id, actor, action, scope, target, old_value, new_value, rationale_id, rationale_excerpt, timestamp_utc, request_id Given workspace retention policy is set to 7 years When an audit record exceeds 7 years of age Then it is purged within 24 hours by a retention job and the purge event is itself logged Given an admin requests an export for a date range and team scope in CSV or JSON When the export is requested Then a downloadable file is generated within 5 minutes including all matching records and a SHA-256 checksum Given a non-admin requests an export When the request is made Then the system returns 403 and no export is produced
Effective-Dated Scheduling and Rollback
Given a weight change includes activation_at in the future When the change is approved Then the change is queued as Scheduled and remains inactive until activation_at Given a scheduled change exists for the same role and team When a new overlapping scheduled change is submitted Then the submission is rejected with validation error "Overlapping scheduled change exists" unless the prior is canceled Given activation_at is reached When the scheduler runs Then the change is applied within 1 minute, an audit event is recorded, and notifications are sent Given a scheduled change includes end_at When end_at is reached Then the system automatically reverts to the prior effective weight and records a rollback audit event Given a manual rollback is triggered on an active change When the rollback is confirmed Then a new change is created to restore the previous weight immediately or at a specified time and follows the same approval policy
Publishing Notifications to Impacted Teams
Given a weight change is published (activated) for Team A When publication occurs Then all rotation members and managers in Team A receive a notification via email and in-app within 5 minutes including: role, old→new weight, rationale excerpt, effective time, approvers, and link to details Given a notification delivery attempt fails When the first attempt fails Then the system retries at least 3 times over 15 minutes and surfaces failure in admin alerts Given a user has opted out of email but not in-app notifications When publication occurs Then only an in-app notification is delivered
Read-Only Rationale Visibility for Rotation Members
Given a user is a rotation member of Team A without Weight.Viewer permission When they view rotation details Then they can see rationale text, author, and timestamp for active and scheduled weight changes, but numeric weight values are hidden Given a rotation member attempts to edit or delete a rationale When the action is attempted Then the request is denied with 403 and no changes are made Given access via API with the member's token When requesting GET /teams/{id}/weights/rationales Then the API returns rationale metadata without weight fields and enforces scope to Team A
Simulation & What‑If Preview
"As a team lead, I want to simulate the impact of weight changes before publishing so that I can tune policies without disrupting the team."
Description

Offer a sandbox where leads can adjust weights and preview the next 30–90 days of assignments across time zones, holidays, and focus blocks without affecting live schedules. Display projected after-hours counts per user, deviation from targets, and risk flags (e.g., cap breaches) for each scenario. Enable side-by-side comparison of multiple scenarios and one-click promotion of a chosen scenario to production, with automatic creation of a change set and rationale prompt.

Acceptance Criteria
Sandbox Weight Changes Are Isolated From Production
Given a production schedule with current role weights configured And the lead opens the Simulation & What‑If Preview When the lead modifies weights and saves a new scenario Then no production assignments or weights are altered And only the scenario’s previewed assignments and metrics update
Accurate 30–90 Day Projection Across Guardrails
Given users with configured work hours, holidays, focus blocks, and FairShare rotation rules And a selectable preview horizon of 30 to 90 days When the lead generates a preview for N days within that range Then the projection spans exactly N calendar days from the selected start And no projected assignment violates work-hour guardrails, holidays, or focus blocks And FairShare uses the scenario’s weights to produce the rotation
Metrics and Risk Flags Are Computed and Visible
Given a generated scenario preview When viewing the scenario metrics Then each user shows projected after-hours assignment count for the horizon And each user shows deviation from weighted target share as a percentage with one decimal And any cap or policy threshold breach is flagged with severity and reason
Side-by-Side Scenario Comparison and Diffs
Given at least two saved scenarios with the same horizon When the lead opens Compare view Then up to four scenarios can be viewed side-by-side And key metrics (after-hours counts, deviations, risk flags) are aligned per user And differences are highlighted per metric and user
One‑Click Promotion Creates Change Set and Rationale
Given a selected scenario in the preview And the lead has permission to promote changes When the lead clicks Promote to Production Then the scenario’s weights are applied to production with an effective timestamp And a change set is created capturing before/after weights and author And a rationale prompt is displayed and any entered text is stored with the change set
Role-Based Access for Simulation and Promotion
Given workspace roles of Lead and Member When a Lead accesses Simulation Then they can create, edit, compare, and promote scenarios And when a Member accesses Simulation Then they can view scenarios and metrics but cannot edit or promote
Performance, Determinism, and Error Handling of Preview
Given a workspace with up to 200 users and a 90‑day horizon When generating a preview Then the preview completes within 8 seconds at p95 and 2 seconds at p50 And repeated runs with identical inputs produce identical projections And transient failures surface a retry option and do not alter production data

Time Debt

Track each person’s cumulative after-hours minutes across series and automatically propose “payback” rotations that restore balance. See a clear debt/credit bar for every attendee and get auto-suggestions that nudge future meetings toward those with lower burden. Reduces resentment and eliminates manual tallying.

Requirements

After-hours Detection Engine
"As a team lead, I want after-hours minutes to be calculated accurately per attendee so that we can fairly measure burden."
Description

Compute per-attendee after-hours minutes for every meeting occurrence by comparing event times to each person’s configured working hours, time zone, holidays, focus blocks, and exceptions. Support recurring series, partial overlaps, and daylight-saving transitions; recalculate deltas when availability rules change. Provide idempotent processing from calendar sources, handle retroactive backfills, and expose normalized outputs (minutes, weighted minutes, reason codes) for downstream features. Ensure performance for near real-time updates and correctness across cross-region scenarios.

Acceptance Criteria
Partial Overlap Single Meeting Calculation
Given an attendee with working hours 09:00–17:00 in their local time and a meeting scheduled 16:30–18:00 local on a non-holiday with no focus blocks or exceptions When the engine computes after-hours for that attendee Then afterHoursMinutes = 60 and inHoursMinutes = 60 for that occurrence And the result is persisted once keyed by {externalEventId, occurrenceId, attendeeId} And computation metadata includes {computedAt, sourceVersion}
Holiday and Focus Block Exclusions
Given a meeting fully within normal working hours that overlaps an all-day holiday for the attendee When the engine computes Then afterHoursMinutes equals the meeting duration and reasonCodes includes ["HOLIDAY"] Given a meeting 13:00–14:00 local overlapping a focus block from 13:15–13:45 with weight 1.5 and base weight 1.0 configured When the engine computes Then afterHoursMinutes = 30 and weightedMinutes = 45 and reasonCodes includes ["FOCUS_BLOCK"] And non-overlapping in-hours minutes are excluded from afterHoursMinutes
DST Transition Handling for Recurring Series
Given a weekly meeting defined 16:00–17:00 UTC with an attendee in America/Los_Angeles (working hours 09:00–17:00) spanning a spring-forward transition When the engine computes per occurrence Then the occurrence before DST maps to 08:00–09:00 local with afterHoursMinutes = 60 and reasonCodes includes ["OUTSIDE_WORK_HOURS"] And the occurrence after DST maps to 09:00–10:00 local with afterHoursMinutes = 0 And RRULE/EXDATE expansions are honored; overridden instances use override times; canceled instances are removed from outputs within 2 seconds
Idempotent Processing of Calendar Upserts
Given multiple deliveries of the same event occurrence (same iCal UID, sequence/etag, externalEventId, occurrenceId) When the engine processes them N times Then exactly one normalized result exists for that attendee-occurrence and outputHash remains identical across replays And updatedAt does not change after the first successful write And no duplicate rows or double-counting occur in aggregates And semantically no-op updates (e.g., attendee order changes) do not alter outputs
Retroactive Recalculation on Availability Rule Change
Given an attendee updates availability rules (e.g., working hours, holidays, focus blocks, exceptions) effective at time T When the engine ingests the change Then all impacted attendee-occurrences within the configured backfill window are re-evaluated And only occurrences whose computed values change are re-upserted And a diff record is stored per occurrence with {priorMinutes, newMinutes, deltaMinutes, priorWeighted, newWeighted} And the backfill completes and updated results are available within 5 minutes for up to 50k impacted occurrences
Cross-Region Multi-Attendee Normalization
Given a meeting with attendees in three time zones with different working hours, holidays, and exceptions When the engine computes results Then it outputs one record per attendee-occurrence with fields {attendeeId, occurrenceId, minutes, weightedMinutes, reasonCodes[], segmentBreakdown[]} And per-attendee minutes are computed in that attendee’s local time using their specific rules And integer minute rounding uses round-half-up at segment boundaries for deterministic totals
Near Real-Time Update Latency SLA
Given a new or updated calendar event affecting up to 20 attendees When the engine processes the change Then p95 time from webhook receipt to normalized outputs availability is ≤ 2 seconds and p99 ≤ 5 seconds And under sustained load of 100 events/second for 15 minutes, p95 remains ≤ 2 seconds with zero data loss And events that cannot be processed immediately are queued and retried with eventual availability ≤ 10 minutes
Time Debt Ledger & Accrual Rules
"As a scheduler, I want a simple ledger that tracks debt/credit over time so that I can see who owes payback in a series."
Description

Maintain a durable ledger that aggregates after-hours accruals into per-user debt/credit totals at series and global scopes. Record line items per event occurrence with timestamps, minutes, weights, and source references. Support configurable rules: weighting for early/late windows, caps per day/week, decay/expiration, and grace buffers. Enable manual adjustments with audit trails and comments. Provide consistent APIs for querying current balances, history, and projections, with transactional integrity and backfill safety.

Acceptance Criteria
Event Occurrence Line Item Recording
Given a meeting occurrence results in after-hours minutes for one or more attendees When the occurrence is processed Then a ledger line item is created per affected user with fields [user_id, series_id (nullable), event_id, occurrence_id, occurrence_start_utc, occurrence_end_utc, minutes_after_hours, weight_applied, weighted_minutes, source, timezone_at_event, created_at_utc] And zero after-hours minutes for a user results in no line item for that user And repeated processing of the same occurrence_id for the same user creates at most one line item (idempotent) And per-user series and global totals are updated atomically with the line item write
Weighting for Early/Late Windows
Given organization or series-level weighting rules are configured for defined local-time windows (e.g., Early 05:00–08:00 weight 1.5; Late 18:00–22:00 weight 2.0) When computing a user's after-hours accrual for an occurrence Then each minute is multiplied by the highest applicable weight for that minute and summed into weighted_minutes And if no rule applies, a default weight of 1.0 is used And series-level rules override organization defaults for that series And the applied weight set identifier is stored on the line item
Daily/Weekly Caps and Grace Buffers
Given daily and weekly caps (in weighted_minutes) and a grace buffer G (minutes) are configured When calculating accruals for a user Then any contiguous after-hours segment with duration <= G minutes is not accrued And total credited weighted_minutes per calendar day does not exceed the daily cap And total credited weighted_minutes per ISO week does not exceed the weekly cap And if applying a cap requires truncating a segment, the line item records cap_applied=true and cap_type in {"daily","weekly"} with truncated_minutes
Decay and Expiration of Debt/Credit
Given a decay rule (e.g., 10% reduction per 7 days) and/or expiration rule (e.g., expire after 90 days) with an effective_at timestamp are configured When querying balances as of time T Then decay and expiration are applied as of T to derive balances without mutating original line items And items past expiration are excluded from balance but remain in history with expired=true And rule changes take effect based on effective_at, using the rule version in effect at occurrence_time for history views and at T for balance views, with rule_version returned in responses
Manual Adjustments with Audit Trail
Given a user with ManageTimeDebt permission submits a manual adjustment for a target user at series or global scope with a non-empty comment When the adjustment is saved Then a ledger entry of type="adjustment" is created with fields [adjustment_id, actor_user_id, target_user_id, series_id (nullable), delta_minutes, delta_weighted_minutes (optional), comment, created_at_utc, correlation_id] And balances update atomically to reflect the adjustment And adjustments are immutable; corrections are performed via reversal entries linked by reversal_of_id And the adjustment is visible via history APIs and included in balances
Query APIs: Balances, History, Projections
Given the balances API is called with user_ids, scope in {"series","global"}, series_id (when scope=series), and as_of (ISO8601 UTC) When the request is processed Then the response returns per user: balance_minutes, balance_weighted_minutes, last_updated_at_utc, rule_version, and is computed transactionally (no partial line items) And the history API returns a paginated, filterable list of ledger entries (events and adjustments) with sorting by occurrence_time desc and filters by date range, event_id, series_id, source And the projections API accepts a set of planned occurrences and current rules and returns per user the projected delta_minutes/weighted_minutes and projected balances at specified horizons And all APIs respond within defined SLAs (p95 <= 500ms for cached reads, <= 1500ms for uncached) for up to 100 users
Backfill and Concurrency Safety
Given a historical backfill job runs while live event processing is active When both attempt to write line items for the same user_id and occurrence_id Then at most one line item exists due to a uniqueness constraint on (user_id, occurrence_id) And balances remain correct with exactly-once effects under retries And concurrent writes use transactions with snapshot or serializable isolation; on conflict, the later transaction retries and succeeds without duplicate accrual And backfill is restartable without double-counting and emits metrics for collisions and retries
Debt/Credit Visualization Bar
"As an attendee, I want a clear visual bar showing my current debt or credit so that I understand my standing at a glance."
Description

Deliver an accessible UI component that renders each attendee’s current debt/credit as a horizontal bar with color-coding, numeric totals, and tooltips. Embed in series pages, the scheduler, and participant profiles. Support drill-down to show contributing meetings and adjustments, and preview how a proposed time changes the bar (delta view). Ensure keyboard navigation, WCAG AA contrast, localization of units/labels, and responsive layouts. Cache summaries for fast load while keeping details fresh.

Acceptance Criteria
Accurate debt/credit bar rendering
Given an attendee has 45 minutes of after-hours debt and 0 minutes of credit When the bar component renders Then a horizontal bar segment displays in the debt color and the numeric total "45m" appears with an accessible label "45 minutes after-hours debt" And a tooltip is available on hover and keyboard focus showing "After-hours debt: 45 minutes" Given an attendee has 0 minutes of debt and 30 minutes of credit When the bar component renders Then a horizontal bar segment displays in the credit color and the numeric total "30m credit" appears with an accessible label "30 minutes after-hours credit" And the tooltip shows "After-hours credit: 30 minutes" Given an attendee has a net total of 0 minutes When the bar component renders Then no colored bar segment is shown and the numeric total displays "0" And the tooltip states "No after-hours debt or credit"
Accessible bar: keyboard, screen reader, and contrast compliance
Given the bar is present on a page When navigating with a keyboard Then each attendee’s bar is reachable via Tab in a logical order and has a visible focus indicator with contrast ratio ≥ 3:1 And pressing Enter or Space on the focused bar opens the drill-down details And focusing the info hotspot or bar surface reveals the tooltip within 200ms and the tooltip content is reachable via Shift+Tab without trapping focus Given a screen reader is active When the bar receives focus Then it announces role "button", attendee name, net value (e.g., "45 minutes after-hours debt"), and instructions "Press Enter for details" Given any bar color swatch and its adjacent background When measured Then text and essential visuals meet WCAG 2.1 AA contrast ratio of ≥ 4.5:1 and information is not conveyed by color alone (a text label for debt/credit is present)
Drill-down: contributing meetings and adjustments
Given the user activates an attendee’s bar via click, Enter, or Space When the drill-down opens Then it lists contributing items with columns: Date/Time (localized), After-hours minutes (+/−), Type (Meeting/Adjustment), and Source (series/meeting ID) And items are sorted by most recent first and support filtering by date range and type And manual adjustments are labeled "Adjustment" with note text and author Given the user updates or deletes a contributing meeting in another tab When the drill-down is refreshed or refocused Then the list reflects the change within 5 seconds and the bar total updates accordingly Given the user closes the drill-down When it dismisses Then keyboard focus returns to the originating bar
Delta preview on proposed time changes in scheduler
Given the scheduler is open with multiple attendees visible When the user drags the proposed meeting time or selects a different slot Then each attendee’s bar updates within 100ms to show a delta overlay and a predicted new total And the tooltip on focus/hover shows Before, After, and Delta values (e.g., "Before: 45m debt • After: 30m debt • Δ −15m") Given the user cancels the change When the scheduler reverts to the original time Then all bars revert to their original totals and the delta overlays disappear Given the user confirms the new time When the change is saved Then the predicted totals become the new current totals and the delta overlays are removed
Embedding and responsive layouts across contexts
Given the component is embedded in a series page, the scheduler, and a participant profile When each page loads with network latency ≤ 200ms Then the bar renders in all three contexts with consistent visuals, spacing, and behavior and displays a loading skeleton while data is pending Given viewport widths of 320px, 768px, and 1280px When the bar renders Then labels truncate with ellipsis at 320px with tooltips exposing full text, the bar remains at least 8px tall on mobile and 12px on desktop, and numeric totals remain readable without overlap And no horizontal scrollbars are introduced in any context Given no data is available for an attendee When the component renders Then a zero-state is shown with the text "No after-hours data yet" and no errors are thrown
Localization of labels, numbers, and units
Given the user’s locale is en-US When the bar renders values Then numbers use en-US formatting and units display as m/h with correct pluralization (e.g., "1 hour 5 minutes", "0 minutes") Given the user’s locale is fr-FR When the bar renders values Then numbers use fr-FR formatting and translations are applied via i18n keys, with correct plural forms (e.g., "1 heure 5 minutes") Given a value ≥ 60 minutes When formatted Then the bar displays hours and minutes; for values < 60, minutes only And tooltips, ARIA labels, and drill-down columns all reflect the selected locale and units consistently
Cached summaries with fresh details
Given cached summary data exists for a series When the series page loads Then the bar totals load from cache within 300ms (95th percentile) and display a subtle "Updated just now" freshness indicator Given a meeting affecting after-hours totals is created, edited, or deleted When the change is saved Then the relevant attendee summaries are invalidated and refreshed so that bars reflect the change within 5 seconds Given a user opens drill-down details When data is fetched Then the details bypass stale cache and return fresh data or a validated ETag within 1.5 seconds (95th percentile); if the network fails, the user sees a retriable error and last-known data is labeled "Out of date"
Scheduling Bias Algorithm Integration
"As a meeting organizer, I want Timeglue to prioritize slots that ease the burden on over-taxed teammates so that schedules trend toward fairness without manual math."
Description

Incorporate debt/credit scores into Timeglue’s slot-scoring engine to nudge proposed windows toward lower-burden participants while honoring all hard constraints (work hours, holidays, focus blocks). Provide tunable bias strength and guardrails to avoid extreme or unfair outcomes. Expose explanation metadata (e.g., “preferred due to Alice’s high debt”) in the UI and API. Support A/B toggles and per-series configuration, with performance optimizations to keep search responsiveness under SLA.

Acceptance Criteria
Bias Favors High-Debt Attendee Without Breaking Constraints
Given attendees Alice(debt=180), Bob(debt=30), and Chao(debt=0) with configured work hours, holidays, and focus blocks And a candidate set of six 60-minute windows in the next 7 days where each window inconveniences exactly one distinct attendee by 30 minutes after hours and all others are within work hours And bias_strength=0.6 When the engine ranks the candidates Then the top-ranked window inconveniences the lowest-debt attendee among Alice, Bob, and Chao And no returned window violates any attendee’s work hours, holidays, or focus blocks And the top 3 windows collectively inconvenience higher-debt attendees no more than lower-debt attendees (count_high_debt_inconvenienced <= count_low_debt_inconvenienced)
Tunable Bias Strength Produces Monotonic Effect and Baseline Parity at s=0
Given a fixed candidate set and debt scores for attendees And two runs with bias_strength=0 and bias_strength=0.8 When the engine ranks the candidates Then the ordering at s=0 matches the baseline engine exactly (rank positions identical for top 10) And the proportion of top 5 windows that minimize burden for the highest-debt attendee is greater at s=0.8 than at s=0 And setting bias_strength outside [0,1] is rejected with validation error "bias_strength_out_of_range"
Guardrails Prevent Extreme or Unfair Outcomes
Given bias_strength=1.0 and a candidate set that includes windows that would start more than 60 minutes outside some attendee’s work hours When the engine ranks the candidates Then no window whose start is >60 minutes outside any attendee’s work hours appears in the top 3 if there exists a candidate with <=30 minutes outside for all attendees And the debt bias contribution to the total slot score is capped at 30% of the overall score And in exact-tie situations, ranking uses a stable round-robin tiebreaker based on prior series history
Explanation Metadata in UI and API for Bias Decisions
Given bias is enabled with bias_strength=0.6 When fetching proposed windows via API v1/proposals and viewing the UI slot list Then each returned slot includes metadata: score, components.debt_bias (float), components.hard_constraints (float), favored_attendees[], disadvantaged_attendees[], rationale (string) And at least one slot has rationale containing "preferred due to" and the highest-debt attendee’s name And when bias_strength=0 or bias is disabled for the series, components.debt_bias=0 and rationale="debt_bias_disabled", and the UI shows a "Bias off" indicator
Per-Series Configuration and A/B Toggle Control Behavior
Given a meeting series S with bias_enabled=true, bias_strength=0.7, and series-level guardrail overrides And a global default bias_strength=0.4 When generating proposals for S Then the series-level settings are applied instead of global defaults And toggling S to bias_enabled=false produces rankings identical to baseline (top 10 order equal; scores within 1e-6) And users assigned to the control cohort remain in control for the entire series (assignment sticky across sessions)
Performance SLA with Bias Enabled
Given a reference environment (4 vCPU, 8 GB RAM) and dataset: 5 attendees, 2 time zones each, 40 focus blocks total, 10 holidays total, search horizon 14 days, top_k=20 And bias_enabled=true with bias_strength=0.6 When executing 1000 proposal searches Then p95 latency <= 400 ms and p99 latency <= 700 ms And average CPU utilization increase <= 10% over baseline and memory overhead <= 10% And throughput degradation <= 5% compared to baseline at 95th percentile
Payback Rotation Generator
"As a series owner, I want an auto-generated rotation that restores balance so that payback is planned and transparent."
Description

Generate fair rotation plans for recurring meetings that assign inconvenient times in a balanced sequence to reduce accumulated debt. Simulate future occurrences, preview impact on each attendee’s balance, and propose adjustments when participants join/leave or are on holiday. Allow organizers to accept, edit, or apply the rotation to the series, with change tracking and rollback. Export rotation summaries to calendar descriptions/notes and provide API endpoints for automation.

Acceptance Criteria
Initial Rotation Generation for Recurring Standup
Given a recurring meeting series with defined candidate time windows, attendee working hours, holidays, focus blocks, and current after-hours debt in minutes for each attendee, and K=12 upcoming occurrences When the organizer runs the Payback Rotation Generator for the next K occurrences Then the generator selects a time for each occurrence from the candidate windows, computes per-attendee after-hours minutes for that time, and produces a rotation plan for all K occurrences And the projected cumulative after-hours debt standard deviation across attendees after K occurrences is less than or equal to the baseline (prior to generation) And no single swap of two occurrence assignments yields a lower maximum projected debt across attendees (local optimality) And hard constraints are respected (no occurrence overlaps attendee holidays or hard focus blocks) And the output includes for each occurrence: ISO 8601 datetime with timezone, chosen window label, per-attendee minutes delta, total minutes delta, and rationale code And generation completes within 5 seconds for K≤24 and N≤10 attendees
Simulation Preview Summarizes Per-Attendee Debt Impact
Given a generated rotation plan for K upcoming occurrences When the organizer opens the Simulation Preview Then each attendee displays: current debt (minutes), projected change (minutes ±), and projected final debt (minutes) And attendees can be sorted by projected final debt descending, ascending, or alphabetically And expanding an attendee reveals a list of occurrences where that attendee accrues >0 after-hours minutes with the amount per occurrence And a fairness alert is shown if any attendee’s projected final debt exceeds the team mean by >60 minutes And all previewed totals exactly match the plan’s computed values (no rounding drift greater than ±1 minute)
Dynamic Rebalancing on Attendee Join or Leave
Given a rotation plan exists for a recurring series and an attendee is added or removed effective a given date, or an attendee’s working hours change When the generator is re-run or an automatic re-evaluation is triggered Then a revised plan is proposed starting from the next unsent/ unlocked occurrence, leaving all past and locked occurrences unchanged And the proposal includes a diff: count of occurrences changed, per-attendee net minutes shift (±), and updated projected final debts And fairness constraints from the initial generation remain satisfied (no increase in maximum projected debt if a single swap would reduce it) And if constraints cannot be met due to insufficient candidate windows, the plan is marked Infeasible and lists the specific occurrences and conflicting constraints And rebalancing completes within 5 seconds for K≤24 and N≤12 attendees
Holiday and Focus-Block Respect with Weighted Soft Constraints
Given attendee calendars include holidays/OOO and focus blocks labeled as Hard or Soft When the generator evaluates candidate times for each occurrence Then any time overlapping a Hard block or holiday for any attendee is excluded from consideration And times overlapping Soft blocks are permitted but add a penalty equivalent to +30 after-hours minutes for the affected attendee for balancing calculations And the chosen rotation contains no Hard overlaps and minimizes total (after-hours minutes + soft penalties) across occurrences while preserving fairness constraints And the plan’s output lists any Soft overlaps with attendee name, occurrence, and penalty applied
Organizer Override, Apply to Series, and Rollback with Locks
Given a generated rotation plan is visible to the organizer When the organizer manually changes an occurrence’s assigned time within allowed candidate windows or reassigns inconvenience designation Then per-attendee projections recalculate within 500 ms and the occurrence is marked Manual with a lock icon And subsequent auto-generations do not modify locked occurrences unless the organizer explicitly Unlocks them And when the organizer clicks Apply to Series, all future calendar events in scope are updated to the chosen times and the plan version is incremented And the system maintains a versioned history (at least 20 prior versions) and supports Rollback to any prior version, restoring event times and descriptions accordingly within 60 seconds And all changes (who, what, when) are recorded in change logs accessible from the series
Export Rotation Summary to Calendar Descriptions/Notes
Given a plan is applied to the series When events are updated Then each updated event’s description is appended (preserving existing content) with a Rotation Summary section containing: rotation plan ID, version, occurrence owner/region, and per-attendee projected final debt And an X-TIMEGLUE-ROTATION-ID property is added to the event metadata where supported (ICS extension) And times shown in the summary include the event time in organizer’s timezone and each attendee’s local time And the export completes within 60 seconds of Apply and is retried up to 3 times on transient calendar API failures And the organizer can copy a plain-text summary of the next 4 occurrences from the UI
Rotation API for Generate, Preview, Apply, and History
Given authenticated clients with OAuth2 access and a seriesId When the client calls POST /rotation/generate with seriesId, horizon (default 12), and idempotencyKey Then the API responds within 2 seconds with 200 and returns: rotationId, version, occurrences[], per-attendee projections, and rationale codes; duplicate idempotencyKey returns the original result And GET /rotation/{rotationId} returns the latest version; POST /rotation/{rotationId}/apply applies the plan; POST /rotation/{rotationId}/rollback with toVersion restores that version And all endpoints are rate-limited (e.g., 60 requests/min per client); requests exceeding the limit receive 429 with Retry-After And write operations are atomic; concurrent conflicting applies return 409 Conflict with guidance to retry fetching latest version And a webhook event rotation.applied is emitted within 10 seconds of a successful apply including rotationId, version, and affected occurrence IDs
Notifications & Nudges
"As an attendee, I want discreet nudges when I’m accruing too much debt so that I can propose alternatives before resentment builds."
Description

Deliver configurable in-product and email/Slack notifications that alert users and organizers when debt thresholds are crossed, when payback rotations are ready for review, or when a proposed time increases a specific attendee’s debt. Include pacing and batching to prevent spam, quiet hours, and role-based recipient rules. Each notification should provide clear context and deep links to the relevant view (ledger, proposal, or scheduler). Respect user preferences and org policies.

Acceptance Criteria
Debt Threshold Crossed Notification Delivery
Given a user has a configured personal debt threshold and an org policy threshold And a meeting is scheduled or updated causing the user’s cumulative after-hours minutes to cross the lower of those thresholds When the crossing event is recorded Then send a notification within 1 minute via all channels enabled for the user (in-product, email, Slack) And include: triggering meeting, delta minutes, new total debt, threshold name/value, user timezone, and a deep link to that user’s ledger view And suppress duplicate notifications for the same threshold crossing per user for 24 hours And respect the org pacing window set to 15 minutes by batching multiple crossings into one message with a count and list And if the user’s quiet hours are active, queue the message and deliver at the end of quiet hours local time
Payback Rotation Ready Notification
Given a payback rotation proposal is generated for a meeting series and enters “Ready for Review” state When the proposal status changes to Ready for Review Then notify recipients per role-based rules (organizer(s) mandatory, team admin if configured, attendees optional per policy) And deliver only via channels enabled by each recipient And include a summary (affected attendees, proposed dates, net minutes restored) and a deep link to the proposal view And do not notify recipients who opted out unless an org policy requires override (mark such notifications as Policy) And cancel any pending notifications if the proposal is superseded before delivery
Scheduler Nudge for Debt Increase
Given an organizer is selecting or dragging a proposed time in the scheduler And the proposed time would increase after-hours minutes for at least one attendee When the time is highlighted or dropped into place Then show an in-product nudge within 300ms indicating each affected attendee’s projected debt increase (minutes delta and new total) And present up to 3 alternative slots within the organizer’s acceptable hours that minimize total team debt over the next 14 days And allow an override with a required reason if the organizer proceeds with the higher-debt time And log the override reason and attach it to the meeting note And suppress the nudge if the projected increase per attendee is below the configurable nudge threshold (default 5 minutes)
Preference and Policy Enforcement
Given user-level notification preferences and org-level policies are configured When any notification or nudge is triggered Then send only via channels permitted by both user preferences and org policy, otherwise fall back to in-product only And honor per-user quiet hours and do not deliver push/email/Slack during those hours (queue for later) And exclude deactivated users and external guests unless explicitly allowed by policy And include a manage-preferences deep link in email and Slack notifications And record whether delivery complied with user preference or was policy-overridden
Deep Link Integrity and Context
Given a notification includes a deep link to a ledger, proposal, or scheduler view When the recipient clicks the link Then route to the correct view with the correct entity preselected and context applied within 2 seconds TTFB And preserve query parameters (source, channel, recipient id) for analytics And enforce permissions (return 403 or request access if unauthorized) And if not authenticated, redirect to login and then back to the target view And for Slack, unfurl the link with title, key metrics, and status; for email, use a signed token that expires in 7 days
Delivery Fallbacks, Pacing Caps, and Audit Logging
Given delivery to a chosen channel fails (e.g., Slack OAuth revoked, email bounces) When the failure is detected Then retry up to 3 times with exponential backoff (1m, 5m, 15m) And fall back to the next available channel per the recipient’s preferences And cap total outbound notifications per user to 5 per hour excluding in-product badges And aggregate non-urgent items into a daily digest delivered at 9:00 AM local time if more than 3 items are queued And write an audit log entry for each send attempt with timestamp, channel, outcome, error (if any), recipient, trigger type, and correlation id
Privacy & Role-Based Visibility Controls
"As an admin, I want to control who sees debt metrics and how long we keep them so that we protect privacy while enabling fairness."
Description

Implement granular permissions and privacy settings defining who can view individual debt metrics, aggregated team views, or anonymized summaries. Offer per-user consent, opt-out for sensitive contexts, and data retention windows. Enforce permission checks across UI, exports, and APIs; log and audit manual adjustments and access to sensitive data. Provide secure exports with PII controls and comply with regional privacy requirements while preserving feature utility for leads.

Acceptance Criteria
Aggregated team debt dashboard for leads
Given a user with role "Team Lead" and permission "view_team_aggregates", When they open the Team Debt dashboard, Then they see only aggregated metrics (sum/avg/min/max) per team or segment, with no names, emails, or direct identifiers for any non-consenting users. And Given fewer than 3 members exist in a segment, When the dashboard would render that segment, Then the segment is suppressed or combined to meet a k>=3 anonymity threshold. And Given a user has not provided consent, When the lead attempts drill-down, Then only "Anonymous N" placeholders are shown and personal trends are not available. And Given the lead lacks "view_team_aggregates", When they access the dashboard URL, Then a 403 is returned and no sensitive data is present in the payload or UI.
User manages consent for personal debt visibility
Given a signed-in user, When they navigate to Privacy Settings, Then they can toggle "Share personal Time Debt metrics with org leads" and see the current state. When the user revokes consent, Then within 60 seconds the system hides their personal metrics from all non-self views, prevents inclusion in new exports, and returns 403 to API calls requesting their data by others. Given a meeting series marked "Sensitive context", When personal debt is calculated, Then those series are excluded from shared personal metrics and aggregates unless the user explicitly opts in for that series. Then an audit log entry is recorded for each consent change with timestamp, actor, prior state, new state, and reason.
Consistent permission checks across UI, API, and exports
Given a user with role "Member", When requesting any personal debt for another user via UI, API, or export, Then the request is denied with 403 and the response contains no sensitive fields. Given a user with role "Team Lead" without permission "view_personal_piis", When requesting individual user metrics, Then the system denies with 403 even if UI elements are manipulated or hidden parameters are used. Given a user with role "Admin" and permission "configure_privacy", When accessing privacy policies, Then they can change policies but cannot view individual debt metrics without "view_personal_piis". Then for every endpoint and export job, permission checks are enforced server-side and covered by automated tests for allowed and adversarial paths.
Audit and adjustment logging for sensitive data
Given a manual adjustment to a user's Time Debt (e.g., applying payback), When saved, Then an immutable audit record is created capturing actor, subject, fields changed, before/after values, justification, timestamp, and source (UI/API). Given any access to a user's personal debt (UI view, API retrieval, export generation), When it occurs, Then an audit event is written with accessor identity, subject user, fields accessed, purpose code, request origin, and correlation ID. Given a compliance officer with role "Compliance", When they filter audit logs by subject user and date range, Then results return within 5 seconds and are exportable in CSV with PII redaction toggle. Then audit logs are retained for at least 13 months and are tamper-evident (append-only with checksum).
Secure export with PII controls
Given a user initiates a Time Debt export, When they choose the privacy level, Then options include: Aggregate-only, De-identified (stable hashed IDs), and Full PII (requires "view_personal_piis"). Given non-consenting users exist, When generating De-identified or Full exports, Then their personal rows are excluded or replaced with "Anonymous N" per k>=3 rule, and series marked Sensitive are omitted. Then the generated file is encrypted at rest, the download URL is single-use, expires in 15 minutes, requires re-auth, and is transmitted over TLS 1.2+. Then each export includes a manifest stating export type, consent snapshot version, time range, applied redaction rules, requester, and checksum.
Data retention and deletion of Time Debt metrics
Given an org-level retention policy is set to N months, When the nightly retention job runs, Then personal Time Debt records older than N months are permanently deleted or anonymized, and aggregates are recomputed accordingly. Given a user's Right to Erasure request is received, When processed, Then all their personal Time Debt data and identifying references in exports and caches are deleted within 30 days (except legally required records), with a non-PII audit entry recorded. After deletion/anonymization, When any previously issued export link is accessed, Then the link is invalidated and returns 410 Gone. Then a retention report is available showing counts of records deleted/anonymized per run.
Regional privacy compliance enforcement
Given an organization marked "EU", When a new user is added, Then no personal Time Debt processing or sharing occurs until the user provides explicit consent; they are excluded from aggregates. Given region-specific data residency is set, When data is stored or exported, Then storage occurs in the configured region and exports are blocked or routed per policy, with an explanatory error if disallowed. Given an EU user withdraws consent, When future meetings are processed, Then personal debt updates for that user cease and prior personal data is deleted/anonymized per retention policy, with notice to leads. Then a consent banner and record of lawful basis and timestamp are presented to EU users, retrievable via API by the Compliance role only.

Holiday Swap

When an upcoming rotation collides with a participant’s holiday or culturally sensitive period, FairShare automatically offers the next fair slot or a cross-swap that preserves the longer-term equity curve. No manual replanning, no surprise late nights before or after time off.

Requirements

Global Holiday & PTO Integration
"As a team lead, I want participants’ holidays and PTO to be automatically recognized so that rotations avoid scheduling conflicts without manual tracking."
Description

Integrate public holiday calendars by country/region and culturally sensitive periods with company calendars and personal PTO sources (Google Calendar, Microsoft 365, ICS). Normalize events into a unified, time zone–aware “availability exclusions” service mapped to each participant. Support full- and partial-day observations, multi-day periods (e.g., Ramadan, Lunar New Year), and half-days. Provide admin controls to select recognized sources, override or add custom dates, and set refresh cadence. Include privacy controls for personal PTO (visibility as “busy/holiday” only) and fallback manual inputs for users without connected calendars. Expose an API for other Timeglue services to query holiday/PTO windows during scheduling and swap computation.

Acceptance Criteria
Map Public Holidays by Region to Availability Exclusions
Given a participant with country=DE and region=BE and recognized sources include Germany (national) and Berlin (regional), When the system refreshes exclusions, Then all holidays from those sources within the next 12 months are created as exclusions with correct local start/end boundaries and durations. Given a holiday defined as multi-day in the source that spans a DST change, When exclusions are generated, Then each day’s exclusion aligns to local midnight-to-midnight boundaries for that region. Given the participant’s region is changed from BE to BY, When the next scheduled or on-demand refresh runs, Then Berlin-specific future exclusions are removed and Bavaria-specific exclusions are added within 15 minutes and an audit entry is recorded.
Normalize Personal PTO from Connected Calendars (Google, M365, ICS)
Given a user connects Google Calendar and has an Out of office all-day event on 2025-12-24 in Europe/Paris, When the next refresh runs per configured cadence, Then a full-day exclusion from 2025-12-24T00:00:00+01:00 to 2025-12-25T00:00:00+01:00 is created and stored/served in UTC. Given the user has a timed PTO event 2025-10-02 13:00-17:30 in America/New_York, When synced, Then a single exclusion with those exact local bounds exists and blocks scheduling in that interval. Given the user adds, removes, or updates PTO-tagged events in source calendars, When the refresh runs, Then corresponding exclusions are created, removed, or updated within one cadence interval and duplicates across multiple sources are deduplicated by UID. Given an ICS feed with X-WR-TIMEZONE=Asia/Kolkata and recurring all-day entries, When imported, Then exclusions are generated per occurrence with correct local boundaries and series exceptions respected.
Support Partial-Day, Half-Day, and Multi-Day Observations
Given a region’s holiday source defines a half-day on 2025-12-24 from 12:00 to 17:00 local time, When exclusions are generated, Then a partial-day exclusion exists exactly 12:00-17:00 local. Given a cultural period with reduced hours 09:00-15:00 from 2025-03-01 to 2025-03-29 for a participant, When applied, Then daily partial exclusions are created for each date inclusive with correct local bounds and count equals 29. Given overlapping exclusions from different sources on the same day, When stored, Then they are merged for scheduling queries without double-counting while preserving source attribution for audit.
Admin Controls: Source Selection, Overrides, and Refresh Cadence
Given a workspace admin selects holiday sources (country/region, cultural sets) and company calendars, When changes are saved, Then only selected sources populate exclusions for members of that workspace. Given the admin sets a refresh cadence of 60 minutes, When observed over a 24-hour period, Then automatic refreshes occur within ±10% of cadence and an on-demand Refresh now completes within 2 minutes for a 500-user workspace. Given the admin adds a custom exclusion (scope=Team A, 2025-11-27 full-day), When saved, Then all Team A members receive that exclusion and other teams do not. Given the admin overrides a source holiday (e.g., disable 2025-05-01 for region FR-75), When refreshed, Then that exclusion is removed for affected participants and the audit log records actor, timestamp, scope, and change summary.
Privacy Masking for Personal PTO
Given a personal PTO exclusion originates from a connected personal calendar, When viewed by anyone other than the owner in UI or via API, Then title, description, location, attendees, and source event IDs are hidden and the event appears only as type=pto with start/end, tz, privacy=masked, and provenance. Given private calendar events not tagged as PTO/OOO, When syncing, Then they are not imported as exclusions unless user/admin-configured keywords match. Given the user disconnects PTO sync or toggles privacy off, When the next refresh runs, Then all personal PTO–sourced future exclusions are removed within one cadence interval and are no longer returned by the API.
Manual Holiday/PTO Fallback for Unconnected Users
Given a user without connected calendars opens the manual exclusions UI, When they create a full-day, partial-day, or multi-day exclusion with a selected time zone, Then the entry validates (non-empty title optional, start<end, within max 1-year span), saves, and appears immediately in their exclusion list and API. Given manual exclusions are created with a specified time zone, When the user later changes their profile time zone, Then existing future manual exclusions retain original local wall-clock times relative to the originally selected time zone and are correctly converted for display/API in UTC. Given a manual exclusion overlaps an existing exclusion, When saved, Then the system produces a non-overlapping canonical set for queries while retaining provenance=manual in audit metadata.
Availability Exclusions Query API for Scheduling and Swaps
Given a service calls GET /availability-exclusions with participant_id, window_start, and window_end, When the request is valid, Then the response is 200 with exclusions normalized to UTC including id, participant_id, start, end, type (holiday|pto|custom|cultural|company), source, tz, privacy, provenance, last_modified. Given a batch request with up to 200 participant_ids and a 30-day window, When executed, Then p95 latency ≤300 ms and payload size ≤2 MB with pagination provided when exceeded. Given If-None-Match is sent with a valid ETag and no changes since last refresh, When called, Then the API returns 304 Not Modified. Given input validation fails (invalid time range or unknown participant), When called, Then the API returns a 4xx with error code and message and no partial data. Given the scheduling or swap engine queries for a time range with exclusions present, When results are computed, Then the union of exclusions is returned without unintended gaps/overlaps and all calculations honor participant local time zones.
Rotation Collision Detection
"As a scheduler, I want holiday collisions to be detected proactively so that I can resolve them before confusing or after-hours invites go out."
Description

Continuously scan upcoming rotation instances for each meeting series to detect overlaps with recognized holiday/PTO windows. Run both event-driven (on new holiday/PTO events or schedule changes) and scheduled jobs (e.g., hourly/daily) within a configurable lookahead horizon (e.g., 90 days). Respect guardrails such as work hours, focus blocks, and blackout days. Flag collisions when overlap exceeds a configurable threshold (minutes or percent of meeting duration). De-duplicate multi-participant conflicts and consolidate into a single incident. Surface incidents in the Timeglue UI and emit domain events to trigger the swap proposal flow.

Acceptance Criteria
Scheduled Scan Detects Holiday Collisions Within Lookahead
Given lookahead_horizon_days = 90 and scheduled_scan cadence is configured And a rotation instance occurs 30 days from now And a participant has a holiday event overlapping the rotation by 20 minutes And threshold_type = minutes and threshold_value = 15 When the scheduled scan executes Then only rotation instances with start times within 90 days are evaluated And a collision is flagged for the instance and participant because 20 > 15 And the incident records overlap_minutes = 20 and overlap_percent = round(100 * overlap_minutes / meeting_duration_minutes) And no collisions are generated for instances starting after 90 days
Event-Driven Detection on New Holiday/PTO or Schedule Change
Given event-driven triggers subscribe to create/update/delete of PTO/holiday events and rotation schedule updates And threshold_type = percent and threshold_value = 30 And a rotation instance exists within the lookahead horizon When a new PTO event is received that overlaps 35% of that instance Then a collision incident is created for series_id + instance_id with cause = holiday and participant listed And the incident stores overlap_percent = 35 and threshold_type = percent and threshold_value = 30 When the PTO event is updated or the rotation time changes such that overlap_percent < 30 Then the incident is marked resolved and no longer appears as active
Configurable Collision Threshold (Minutes and Percent)
Rule: Overlap must strictly exceed the configured threshold to be considered a collision Given threshold_type = minutes and threshold_value = 15 When overlap_minutes = 10 Then no collision is created When overlap_minutes = 15 Then no collision is created (equals threshold) When overlap_minutes = 20 Then a collision is created Given threshold_type = percent and threshold_value = 30 When overlap_percent = 29 Then no collision is created When overlap_percent = 30 Then no collision is created (equals threshold) When overlap_percent = 31 Then a collision is created And overlap_percent is computed as round(100 * overlap_minutes / meeting_duration_minutes) using standard rounding
Guardrails Applied: Work Hours, Focus Blocks, Blackout Days
Given a participant has work hours, focus blocks, and blackout days configured in their profile And a rotation instance falls partially within one or more guardrail windows And threshold_type/threshold_value are configured When the overlap between the rotation and any guardrail window strictly exceeds the threshold Then a collision is created even if no holiday/PTO exists And the incident cause includes the applicable guardrail type(s) (e.g., work_hours, focus_block, blackout_day) And overlap calculations are evaluated in the participant’s local time zone
Multi-Participant De-duplication into Single Incident
Given a single rotation instance overlaps qualifying windows for multiple participants When collision detection runs Then exactly one incident is created per series_id + instance_id And the incident contains a participants array listing each impacted participant with their overlap_minutes and overlap_percent And rerunning detection does not create additional incidents for the same series_id + instance_id; the existing incident is updated to add/remove participants as needed
Incident Surfaced in UI and Domain Event Emitted
Given an active collision incident exists for a meeting series rotation instance When the series detail page is opened in Timeglue Then the incident appears in the Rotation Collisions panel showing next occurrence datetime (series timezone), impacted participants count, primary causes, and a Propose Swap action And opening the incident shows per-participant overlap metrics and causes When a collision incident is created or updated Then a domain event (RotationCollisionDetected or RotationCollisionUpdated) is emitted with payload including incident_id, series_id, instance_id, participant_ids, causes, overlap_minutes/percent (per participant), threshold_type/value, and incident status
Idempotent Re-scans Do Not Create Duplicates
Given scheduled scans and event-driven scans may evaluate the same rotation instance concurrently And an existing incident already represents the collision for series_id + instance_id When the same collision is detected again without any change to participants or overlap metrics Then no new incident is created And no additional domain event is emitted for the unchanged state When any incident attribute changes (e.g., a participant is added/removed or overlap metrics change) Then the existing incident is updated and exactly one corresponding update event is emitted
Fair Reallocation & Cross-Swap Engine
"As a participant, I want any holiday conflict to be resolved by fair swaps so that my time off doesn’t create inequity or after-hours burden later."
Description

Compute the next fair slot or cross-participant swap that preserves the longer-term equity curve for rotating meetings. Respect constraints: participant work hours, focus blocks, holiday windows, duration, and earliest/latest bounds per region. Simulate fairness impacts across a defined horizon to choose the minimal-disruption option. Provide deterministic tie-breakers, respect participant availability preferences, and avoid creating after-hours burdens before/after time off. Produce a rationale (inputs, constraints, fairness deltas) alongside proposals. Return one or more ranked options for consent/policy evaluation.

Acceptance Criteria
Holiday Conflict—Next Fair Slot Proposal
Given a rotating meeting instance where a participant’s holiday window overlaps their assigned next rotation slot And inputs include durationMinutes, per-region earliestStartLocal/latestEndLocal, participant workHours, and focusBlocks And fairnessHorizonDays is configured When the engine generates a next fair slot proposal Then it returns at least one feasible slot on or after the conflicted date that fits durationMinutes, falls within all regions’ earliestStartLocal/latestEndLocal, is within each impacted participant’s workHours, and does not overlap any participant’s focusBlocks or holiday windows And the top-ranked proposal has the lowest disruptionScore over fairnessHorizonDays among all feasible options And the response includes start/end in ISO 8601 with timezone, disruptionScore (numeric), and rank starting at 1
Cross-Participant Swap Preserves Equity Curve
Given at least two participants are eligible for a cross-swap to resolve a holiday conflict And company policy permits cross-swaps And maxEquityDeltaEpsilon and equityTolerancePercent are configured When the engine evaluates cross-swap options Then it proposes a swap only if both resulting slots satisfy durationMinutes, per-region bounds, workHours, focusBlocks, and holiday windows And the post-swap equity metric over fairnessHorizonDays increases by no more than maxEquityDeltaEpsilon relative to the best non-swap feasible option And each participant’s cumulative share remains within equityTolerancePercent of the target equity curve And the proposal includes consentRequired true/false, the participants who must consent, and remains non-actionable until required consents are recorded
Deterministic Tie-Breakers for Equal Disruption
Given two or more feasible proposals have equal disruptionScore (difference ≤ 1e-6) When the engine ranks proposals Then it orders them deterministically by: (1) lower afterHoursBurdenCount, (2) earlier startDateTime, (3) lexicographically smaller assignee participantId, (4) lexicographically smaller proposalId And repeated runs with identical inputs produce identical rankings and proposalIds
Rationale and Audit Payload
Given the engine returns proposals When inspecting the payload Then each proposal contains a rationale object including: inputSnapshotId; participants considered; constraints applied (durationMinutes, region bounds, workHours, focusBlocks, holiday windows); evaluatedOptionCount and rejectedOptionCount; top rejection reasons with counts; per-participant fairness deltas; equity metric pre and post; applied tieBreakerChain; consent flags and required actors; deterministic hash of inputs+proposal And the top-level payload includes auditId, generatedAt (ISO 8601), engineVersion, and scoringParameters used
Availability Preferences and Focus Blocks Enforcement
Given participants have availabilityPreferences (hard or soft) and focusBlocks When the engine generates proposals Then no proposal violates any hard availabilityPreference or any focusBlock And violations of soft availabilityPreferences increase disruptionScore by the configured penalty weight per breach and are listed in rationale.penalties with weights applied And proposals that would violate hard preferences are marked infeasible with reason code PREFERENCE_HARD_BLOCK
After-Hours Buffer Before/After Time Off
Given a participant has an upcoming holiday window And afterHoursBufferHoursBefore and afterHoursBufferHoursAfter are configured When generating proposals or swaps to resolve the conflict Then no proposal schedules that participant outside their workHours within afterHoursBufferHoursBefore hours before or afterHoursBufferHoursAfter hours after the holiday window And any option violating this rule is rejected with reason code AFTER_HOURS_NEAR_TIME_OFF
No Feasible Option Fallback and Recommendations
Given all evaluated next-slot and cross-swap options are infeasible under current constraints When the engine completes evaluation Then it returns zero proposals and a failureOutcome containing: reasonCodes (e.g., NO_FEASIBLE_WITH_CONSTRAINTS); blockingConstraints; impactedParticipants; rankedConstraintRelaxations with predicted feasibility gain and equity impact; guidance for manual override And the API responds with HTTP 200 and outcomeStatus NO_PROPOSAL
Participant Consent & Auto-Apply Policies
"As a team member, I want clear, low-friction control over swap proposals so that changes don’t surprise me and still respect my boundaries."
Description

Provide a policy engine and UX to determine when proposed swaps auto-apply versus require participant consent. Support per-team and per-series rules (e.g., auto-apply for ±30 minutes changes, require consent for cross-day moves). Define response windows, reminders, escalation to team leads, and fallback behavior if no response (keep original, cancel, or apply best-effort safe alternative). Offer one-click accept/decline via email and Slack with in-app detail view and option to propose alternatives within guardrails. Respect DND/quiet hours and localization for notifications.

Acceptance Criteria
Auto-Apply for Minor Same-Day Adjustments (±30m) with Series Policy Override
Given a team default policy that requires consent for all swaps And a series-level policy that auto-applies same-day adjustments up to 30 minutes And a proposed swap that shifts a participant's start time by 25 minutes on the same calendar day And the swap remains within both participants' configured work hours, avoids focus blocks, and does not fall on a holiday When the policy engine evaluates the proposal Then the series policy overrides the team default And the swap is auto-applied without requesting consent And both participants receive confirmation notifications in their preferred channels within 2 minutes of application And an audit record is created capturing policy source, before/after times, affected participants, and timestamp
Consent Required for Cross-Day Moves
Given a team policy that requires consent for any cross-day move And a proposed swap moves a participant's rotation from Friday to Monday When the policy engine evaluates the proposal Then consent requests are sent to all affected participants via their enabled channels within 2 minutes And the swap remains pending until all required consents are received And if any participant declines, the proposal is marked Declined and no calendar changes are made And the decision and participant responses are logged with timestamps
Response Window, Reminder Cadence, and Lead Escalation
Given a consent request with a response window of 24 hours and a reminder cadence of 12 hours And the recipient has not responded within 12 hours When the reminder time is reached Then a reminder is sent via the user's allowed channels and queued to avoid quiet hours And the reminder event is recorded with delivery status Given the 24-hour window elapses without all required consents When escalation rules target the team lead Then an escalation notification is sent to the designated lead(s) within 5 minutes And the item status transitions to Escalated and is visible in the dashboard
Fallback Behavior on No Response
Given fallback policy is Keep Original When the response window elapses without all consents Then the original schedule is preserved, the proposal is closed, and participants are notified Given fallback policy is Cancel Swap When the response window elapses without all consents Then the pending swap is canceled, no calendar changes are applied, and stakeholders are notified Given fallback policy is Apply Best-Effort Safe Alternative And the engine can find an alternative within guardrails (same day, within work hours, avoids holidays/focus blocks) When the response window elapses without all consents Then the alternative is applied and notifications are sent And if no alternative satisfies guardrails Then the original schedule is preserved and the proposal is closed
One-Click Accept/Decline via Email and Slack
Given a pending consent request And the system generates single-use, signed, expiring accept and decline links per recipient When the user clicks the accept link within the valid window from email or Slack Then consent is recorded, the proposal state updates in real time, and a confirmation message is shown When the user clicks the decline link within the valid window Then the proposal is marked Declined, all parties are notified, and no calendar changes are made When a link is accessed after expiry or after prior use Then an "expired or already used" message is displayed and the user is directed to the in-app detail view
In-App Detail View and Guardrailed Alternative Proposals
Given a pending consent request When a participant opens the in-app detail view Then they see before/after times localized to their time zone, affected participants, policy basis, and response deadline And they can propose an alternative within guardrails defined by policy (e.g., same day, within ±120 minutes, within work hours, avoids focus blocks/holidays) And the UI prevents submission outside guardrails with clear validation messages And a submitted alternative closes the previous proposal and starts a new consent cycle referencing the alternative And the system limits to at most one counter-proposal per participant per request
Notifications Respect DND/Quiet Hours and Localization
Given a user with quiet hours set from 22:00 to 07:00 local time and a preferred language When a consent request or reminder would be sent during quiet hours Then delivery is deferred to the earliest allowed time within the response window And timestamps in notifications use the recipient's time zone and locale-specific formats And message content is localized to the recipient's preferred language And if the remaining response window ends before quiet hours conclude Then the notification is scheduled for the earliest allowable minute and the escalation window adjusts accordingly
Calendar Writeback & Smart Link Preservation
"As an organizer, I want calendar updates to reflect the swap seamlessly so that attendees see the right time without losing meeting links."
Description

Apply confirmed swaps by updating calendar events for all attendees across Google Workspace and Microsoft 365 without creating duplicates. Preserve meeting links/rooms and series association by using exceptions where possible. Ensure time zone–correct updates, idempotent operations, rollback on partial failures, and consistent attendee notifications. Maintain Timeglue smart link integrity (same URL, updated metadata). Emit webhooks and activity events for integrations (e.g., Slack threads, project trackers).

Acceptance Criteria
Cross-Suite Writeback Without Duplicates
Given a Holiday Swap is confirmed for a meeting with attendees on Google Workspace and Microsoft 365 When the writeback job executes Then exactly one calendar event with the original vendor-specific identifier exists per attendee after processing And no additional events with the same summary/UID/time range are created And attendee RSVP statuses are preserved And the original organizer remains unchanged
Meeting Link and Room Preservation
Given the original event contains a conferencing link (e.g., Meet, Teams, Zoom) and/or a room resource When the swap updates the event time Then the conferencing link value remains identical in the updated event And the room resource assignment remains the same And if the room resource cannot be reserved at the new time, no changes are persisted and the operation reports a failure
Recurring Series Exception Fidelity
Given the event belongs to a recurring series and the swap affects a single occurrence When applying the update Then a single exception is created for that occurrence under the original series And the series identifier (UID/ICalUID) remains unchanged And non-affected occurrences remain unchanged And the exception inherits series metadata (title, description, conferencing) unless explicitly changed by the swap
Time Zone–Correct Rescheduling
Given attendees are in multiple time zones and the move crosses a DST boundary When the event is updated Then the stored UTC start and end times match the intended schedule And each attendee’s calendar displays the correct local date and time for their time zone on the event date And no date roll-over or one-hour drift occurs And platform time zone fields are set to valid identifiers for each vendor
Idempotent Writeback and Deduplication
Given an identical swap writeback request is received multiple times with the same idempotency key When the system processes the request Then the calendar state is identical to a single successful execution And no duplicate events or duplicate notifications are produced And only one webhook/activity event per swap is visible to subscribers, deduplicated by idempotency key And the operation is recorded as an idempotent replay rather than a new mutation
Transactional Rollback on Partial Failure
Given updates must be applied to all attendee calendars atomically When any attendee update fails irrecoverably during processing Then all previously updated calendars are reverted to the original event details within 60 seconds And attendees do not receive mixed-state notifications for the same meeting And the swap is marked failed with an error code and diagnostics recorded And a retry is queued with exponential backoff
Notifications, Webhooks, and Smart Link Integrity
Given the swap is successfully applied When external communications and integrations are triggered Then each attendee receives exactly one update notification (no cancel-and-recreate) reflecting the new time And the Timeglue smart link URL remains unchanged while its metadata (title, date/time, timezone, participants) is updated within 30 seconds And webhooks are delivered at-least-once with a deduplication key and include previous_time, new_time, attendees, series_id, and swap_id And a Slack thread activity and project-tracker event are posted referencing the same deduplication key
Audit Trail & Equity Dashboard
"As a lead, I want visibility into how swaps impact fairness so that I can justify decisions and adjust policies when needed."
Description

Record a tamper-evident audit trail for each detected collision and applied/declined swap, including timestamps, actors, proposed options, selected outcome, fairness scores before/after, and policy decisions. Provide an in-app dashboard to visualize the equity curve over time, highlight impacts of holidays, and export data (CSV/JSON) for compliance or retros. Include configurable retention and privacy redaction for personal PTO details.

Acceptance Criteria
Collision Detection Audit Entry Created
Given a holiday collision is detected for a scheduled rotation When the system generates fair options (next fair slot and cross-swap) Then an audit record is appended containing: collision_id, team_id, actor/service, impacted_participant_ids, detected_reason, timestamp (UTC ISO 8601), proposed_options[] (option_id, start_at, end_at, timezone, fairness_delta, policy_rule_ids[]), and equity_snapshot (team_score, per_member[]) And Then the record is assigned a monotonic sequence_number unique per team And Then attempts to modify or delete the record via API/UI return 403 and are themselves logged as denied_modification with actor and timestamp
Swap Outcome Logged With Fairness Before/After
Given a previously logged collision with proposed options When a swap option is accepted or declined (manual or auto-policy) Then an outcome audit record links to collision_id and includes: decision_actor (user_id or system), decision_type (manual|auto), selected_option_id or "declined", decision_timestamp (UTC), rationale_code (optional), notifications_sent[] (recipient_id, channel, timestamp) And Then fairness_before and fairness_after are recorded at team and per-member levels with the delta per member ≥ -0.5 and ≤ +0.5 points for the event And Then policy_rules_evaluated[] include rule_id, result (pass|fail), and notes (optional)
Tamper-Evident Chain and Verification
Given any new audit record is written When stored Then it includes content_hash (SHA-256 of canonical JSON) and prev_hash referencing the prior record for the same team_id, forming a verifiable chain And When the Verify Audit Chain endpoint is called for a team and date range up to 10,000 records Then the response returns status=valid with first_hash and last_hash within 2 seconds, or status=invalid with the index of the first broken link And Then any attempt to persist a record without content_hash or with mismatched prev_hash is rejected with HTTP 409 and no write occurs
Equity Dashboard Visualization With Holiday Impact
Given a team has at least 12 weeks of rotation and holiday data When a user opens the Equity Dashboard and selects a date range and member filters Then a line chart renders the equity curve (team and per-selected members) at weekly granularity within 1 second for ≤5,000 points And Then holiday-impacted windows are marked with badges; hovering reveals holiday name, affected members, and equity delta vs prior week And Then a tabular view mirrors the chart values and supports CSV copy; both views meet WCAG 2.1 AA (contrast ≥4.5:1, keyboard nav, aria-labels) And Then changing filters updates the visualization within 500 ms client-side (excluding network latency)
Export Audit and Equity Data (CSV/JSON) With Redaction
Given a user with export permission requests an export for a date range When format=CSV or JSON is specified Then a downloadable file is produced within 10 seconds for ≤50,000 records, with timestamps in UTC ISO 8601, numeric dot decimals, LF line endings, and schema keys per export_schema_v1 And Then when privacy_redaction=true, PTO titles, free-text reasons, and external attendee emails are redacted or tokenized; linkage ids (collision_id, outcome_id) remain And Then the export itself is logged in the audit trail with actor_id, range, format, record_count, and file_checksum (SHA-256) And Then exported files include a footer/metadata block with generation_time and schema_version
Configurable Retention and Data Lifecycle
Given an admin sets audit_retention_days=N and redaction_mode in settings When the nightly retention job executes Then records older than N days are deleted or anonymized per redaction_mode, and a summary audit entry logs action, counts_deleted, counts_anonymized, and timestamp And Then if deletion is enabled, a tombstone entry preserves prev_hash linkage so chain verification remains valid And Then deleted/anonymized records are excluded from UI queries and exports, while chain verification reports valid with tombstones included
Role-Based Access and Field-Level Redaction
Given roles Admin, Manager, Member, and Auditor exist When users access the audit trail or equity dashboard Then Admin sees all operational fields; Manager is limited to their teams; Member sees only records where they are actor or impacted; Auditor sees operational fields with PII replaced by stable pseudonyms unless consent is granted And Then unauthorized access attempts return 403 and are logged with actor, resource, and timestamp And Then responses with redaction add no more than 100 ms server-side processing overhead compared to non-redacted responses
Holiday Swap Settings UI
"As an admin, I want a centralized settings panel for Holiday Swap so that I can tailor behavior to my team’s norms and preview impacts before applying changes."
Description

Deliver an admin/settings interface to configure Holiday Swap behavior: recognized holiday sources, cultural periods, fairness model parameters, eligible swap windows, blackout days, auto-apply rules, and notification preferences. Support per-team and per-series overrides with role-based access. Include a simulation/preview tool to test upcoming rotations under different policy settings and see projected equity outcomes before saving.

Acceptance Criteria
Configure Recognized Holiday Sources
Given I am a Team Admin on Team Alpha in Settings > Holiday Swap > Holiday Sources When I add the source "US Federal Holidays" and select region "US" Then the source appears in the enabled list and its holidays appear in the preview calendar for the next 12 months Given a custom iCal URL "https://example.com/team-holidays.ics" When I add it as a holiday source Then the system validates reachability (HTTP 200) and parses at least one VEVENT before allowing Save; otherwise show "Invalid calendar source" and disable Save Given two holiday sources produce events on the same date When a precedence order is configured Then duplicates are merged and the highest-precedence label is shown in the preview Given I disable a holiday source and Save When I reopen the settings Then the previously disabled source remains off and the preview no longer shows its holidays
Define Cultural Sensitive Periods
Given I am in Settings > Holiday Swap > Cultural Periods as Team Admin When I create a period named "Ramadan 2026" from 2026-02-28 to 2026-03-29 and assign it to group "MEA Engineering" Then the period displays in the preview for those assignees and is marked as "Avoid swaps" Given I attempt to create overlapping periods for the same group When I save Then the system warns "Overlapping cultural periods" and requires confirmation or adjustment Given I delete a cultural period When I save Then the preview updates and the period no longer affects simulations
Set Fairness Model Parameters
Given I am in Settings > Holiday Swap > Fairness When I set equity smoothing window = 8 rotations, max deferral = 2 rotations, and enable cross-swap preservation Then the configuration validates numeric ranges and displays updated projected equity curve in the preview Given values outside allowed ranges (e.g., smoothing window < 1 or > 52) When I attempt to Save Then the system blocks with inline validation messages and prevents Save Given I reset to defaults When I confirm Then all parameters revert to system defaults and the preview/equity projection updates accordingly
Configure Eligible Swap Windows and Blackout Days
Given I am in Settings > Holiday Swap > Swap Windows When I set "Next fair slot within" to ±14 days of the conflict and "Eligible rotations" to next 2 rotations Then the preview only suggests swaps within those bounds Given I add blackout days "Company Offsite 2025-10-10" and "Weekends" When I save Then the swap suggestions exclude those days and label conflicts occurring on blackout days as "Cannot swap" Given I change the hosting timezone for evaluation When I preview a collision Then weekend detection respects the hosting timezone, not the viewer's timezone
Auto-Apply Rules and Notifications
Given Auto-Apply is enabled for "Hard conflicts" with minimum lead time = 3 days When a holiday collision is detected 5 days before the rotation Then a swap is applied automatically without manual approval Given team notification preferences of "Notify assignee and series owner via email and Slack on auto-apply" When the auto-apply occurs Then notifications are sent to the configured recipients with the original slot, new slot, and acceptance link/details Given a collision occurs inside the minimum lead time When the engine evaluates it Then it creates a proposal (not applied) and sends "Action required" notifications per preferences
Role-Based Access and Per-Team/Per-Series Overrides
Given roles: Org Admin, Team Admin, Series Owner, Member, Viewer When accessing Holiday Swap settings Then Org/Team Admins can edit team defaults; Series Owners can create per-series overrides; Members and Viewers have read-only access Given a per-series override exists for Series X When a Series Owner changes swap window settings for Series X and saves Then Series X uses the override and other series continue to use team defaults Given I remove the per-series override for Series X When I confirm Then Series X re-inherits team defaults
Simulation/Preview and Equity Outcomes Before Saving
Given there are 12 upcoming rotations and unsaved changes to settings When I click "Run Simulation" Then the preview lists proposed swaps, impacted participants, and displays before/after equity metrics (e.g., max deviation, Gini) without applying changes to live schedules Given I adjust parameters (e.g., max deferral) and rerun the simulation When the results load Then the preview updates and shows deltas versus the previous run Given I click "Save" When save completes Then a new settings version is recorded, simulations clear the "Unsaved" state, and the policy becomes active for future evaluations

Series Balancer

Balance fairness across multiple recurring meetings, not just within one series. Link standups, retros, and reviews so the same person isn’t repeatedly penalized across different cadences. FairShare coordinates rotations to keep each person’s total off-hour impact in a healthy range.

Requirements

Cross-Series Linking
"As a remote team lead, I want to link several recurring meetings into one balance group so that fairness is managed across all of them, not just within each series."
Description

Enable users to group multiple recurring meetings (e.g., standups, retros, reviews) into a single balance group that shares a fairness policy. Provide UI to select series, map participants across series, and define group-level settings such as weighting per meeting type and maximum allowable off-hour impact. Persist groups, support edits and deletion, and enforce permissions so only authorized organizers can link or unlink series. Ensure identity resolution when attendees differ by calendar account or alias, and maintain an audit trail of membership and policy changes.

Acceptance Criteria
Create Balance Group with Multiple Series
Given I am an organizer with the "Manage Series Balancer" permission and own or co-own at least two recurring series When I choose "Create Balance Group", provide a unique name (1–50 chars), and select 2–10 series across connected calendars Then the system validates name uniqueness within my organization, verifies edit rights for each selected series, and highlights any series lacking permission without creating the group When all validations pass and I submit Then the group is created with a unique Group ID, default policy (per-meeting-type weights=1.0, max off-hour impact=2.0 hours/week/person), and links to all selected series within 2 seconds p95 Then the linked series display a "Balanced by [Group Name]" indicator and the group appears in my groups list; state persists across reload and re-login
Participant Identity Resolution Across Aliases
Given selected series include participants represented by different emails or calendar IDs for the same person When I proceed to link the series into a group Then the system auto-suggests identity mappings using exact email match, configured alias domains, and connected identity provider, and shows a review list separating mapped vs. unmapped participants When I approve suggestions and manually map any remaining entries Then each mapped person is assigned a canonical member ID unique within the organization and all series participants are normalized to that ID Then conflicting suggestions (two distinct people mapped to one) are flagged and block group creation until resolved Then confirmed mappings are persisted for reuse in future groups and can be undone by an organizer, with all changes audited
Define Group-Level Fairness Policy
Given a balance group exists and I have organizer permissions When I set per-meeting-type weights between 0.1 and 5.0 (step 0.1) and a max off-hour impact between 0 and 8.0 hours per person per week Then the UI validates ranges, enforces numeric input, and requires justification for values >4.0 hours When I save the policy Then a new policy version is created, a preview of the next 4 weeks of rotations recalculates within 3 seconds p95, and the change is applied to future scheduling decisions Then off-hour impact calculations respect org work hours, holidays, and focus blocks; if constraints make the policy infeasible, a blocking warning lists the reasons and no changes are saved
Edit Balance Group Membership and Settings
Given a balance group exists When I add or remove one or more series from the group Then permissions are revalidated, identity resolution is re-run for newly added participants, and future rotations are recomputed Then removing a series takes effect for instances starting 30 minutes after the change (no mid-meeting disruption), and the removed series retains its standalone fairness settings When two editors attempt overlapping changes Then optimistic locking prevents lost updates and the second editor is prompted to review and reapply their changes Then all edits persist durably and survive service restarts
Delete Balance Group and Data Retention
Given I am a group organizer or org admin When I choose "Delete Group" and confirm the action Then all linked series are unlinked from the group, cross-series balancing stops immediately, and the action completes within 2 seconds p95 Then previously computed future rotations referencing the group are invalidated within 5 minutes and per-series fairness is applied instead Then audit logs and identity mappings remain retained per organization retention policy (minimum 1 year) while the group and its policy are removed from active listings
Permission Enforcement for Linking and Unlinking
Given a user lacks edit permission on any selected series or lacks the organization role to manage Series Balancer When they attempt to create, edit, or delete a balance group Then the system returns 403 Forbidden, lists the missing permissions/series, and makes no changes Given a user is a participant but not an organizer When they view a series that is part of a group Then they see a read-only "Balanced by [Group Name]" indicator but cannot view or modify group policy unless granted "View fairness policy" Then series cannot be linked across different organizations or workspaces; cross-org attempts are blocked with 403
Audit Trail for Group and Policy Changes
Given any group create, update, delete, membership change, identity mapping change, or policy change occurs When the action is committed Then an audit record is written with UTC timestamp (ISO-8601), actor user ID, actor IP or client ID, action type, entity identifiers, and before/after values, and optional reason Then audit records are immutable, paginated, filterable by date range, actor, action, and group, and exportable as CSV via UI or API (<=10,000 records within 10 seconds p95) Then access to audit logs requires org admin or group organizer role; unauthorized access returns 403
Fairness Score Engine
"As a scheduler, I want a clear, consistent fairness score for each participant so that I can see who is bearing off-hour burden across linked meetings."
Description

Implement a FairShare scoring engine that computes per-person off-hour impact across all series in a balance group. Account for working hours, focus blocks, holidays, weekends, time zone offsets, DST changes, meeting duration, and start-time penalties using a configurable severity curve. Recalculate scores incrementally when schedules, attendees, or policies change and expose results via API to the scheduler and UI. Handle partial attendance and required/optional roles, provide deterministic results, and degrade gracefully when inputs are missing or stale.

Acceptance Criteria
Cross-Series Off-Hour Impact Aggregation
Given a balance group with multiple linked series across time zones and persons with defined working hours, focus blocks, holidays, weekends, and time zones When the engine computes scores for a rolling 28-day window Then for each person it outputs personId, balanceGroupId, windowStart, windowEnd, rawOnHourMinutes, rawOffHourMinutes, totalDurationMinutes, totalOccurrencesCount And rawOffHourMinutes equals the sum across all attended occurrences of minutes outside the person’s working hours minus holidays/weekends/focus blocks, evaluated in the person’s local time zone and rounded to the nearest minute (0.5 up) And occurrences spanning working and non-working periods split minutes accordingly And DST transitions are handled so no minute is double-counted or omitted when a meeting crosses a DST boundary And optional occurrences not attended contribute 0 minutes; occurrences attended contribute minutes per attendance data; raw metrics do not apply role weights
Incremental Recalculation on Schedule and Roster Changes
Given a change event to any input (meeting time/recurrence edit, new/cancelled occurrence, attendee add/remove, role change, working-hours/focus-block/holiday policy update, severity-curve update) for a balance group When the event is processed Then only affected persons and occurrences within the relevant time windows are recomputed And unaffected persons’ scores and lastComputedAt remain unchanged And P95 end-to-end recomputation latency per event is <= 2 seconds for groups up to 50 series, 200 total attendees, and 1,000 future occurrences And recomputation is idempotent; reprocessing the same event yields identical scores and does not duplicate audit entries And outputs include updated lastComputedAt timestamps for all recomputed persons
API: Expose Fairness Scores and Metadata
Given an authorized caller with scope fairshare:read and a valid balanceGroupId When the caller requests GET /v1/fairshare/scores?balanceGroupId={id}&windowStart={iso}&windowEnd={iso} Then respond 200 with an array sorted by personId ascending containing personId, balanceGroupId, windowStart, windowEnd, rawOnHourMinutes, rawOffHourMinutes, adjustedOffHourScore, lastComputedAt, stale, assumptionsApplied[] And include an ETag header that changes when any included score changes And support filtering by personId, and pagination via cursor and limit, returning nextCursor when more results exist And return 401 for unauthenticated, 403 for unauthorized balanceGroupId, 404 for unknown balanceGroupId, and 422 with field errors for invalid parameters And if computation is in progress return 200 with stale=true and lastComputedAt unchanged And all timestamps are ISO 8601 in UTC
Roles and Partial Attendance Weighting
Given attendees are marked as required or optional and per-occurrence attendance duration is available When computing adjustedOffHourScore Then required attendance is weighted by 1.0 and optional attendance by wOptional (configurable in [0.5,1.0], default 0.5) And partial attendance scales minutes by attendedDurationMinutes / meetingDurationMinutes And optional absences contribute 0; required absences contribute full minutes unless excused, in which case minutes are weighted by wExcused (configurable, default 0.25) And role/attendance weights are applied per occurrence before the severity curve is applied And per-person overrides of wOptional and wExcused, if present, are used instead of org defaults
Configurable Start-Time Penalty Severity Curve
Given a severityCurve configuration with type in {linear, quadratic, piecewise} and validated parameters within allowed bounds When the engine applies the severity curve to per-occurrence off-hour minutes based on distance of local start time from the nearest working-hours boundary Then adjustedOffHourScore is the normalized sum across occurrences, bounded to [0,100] And updates to severityCurve increment curveVersion and trigger recomputation using the new version; curveVersion is exposed in API responses And invalid configurations are rejected with 422 and do not change existing scores And the penalty function is monotonic non-decreasing with distance from the boundary; automated tests verify sample points And for type=linear with slope s in [0,2], penalty at 60 minutes outside hours equals 60*s ±0.1
Deterministic Results and Idempotency
Given identical inputs (series, occurrences, time zones, policies, roles, attendance, severityCurve) and the same time window When scores are computed multiple times or on different nodes Then numeric outputs match exactly after rounding (durations to nearest minute; scores to 2 decimal places), and arrays are ordered deterministically by personId then occurrenceId And tie-breaking for concurrent events uses (eventTimestamp, eventId lexicographic) to ensure deterministic application order And a responseDigest (SHA-256 of sorted canonical JSON) is emitted and remains identical for identical inputs And re-submitting the same recomputation request with the same idempotency key does not create duplicate work or audit entries
Graceful Degradation with Missing or Stale Inputs
Given missing holiday data for a person’s region When computing scores Then default to organization holidays if available else none, set stale=true, and add assumptionsApplied entry "holidayDataMissing:{region}" Given missing working hours or time zone for a person When computing scores Then default to organization working hours and time zone, add corresponding assumptionsApplied entries, and continue computation Given missing focus block data When computing scores Then treat as no focus blocks and add assumptionsApplied entry Given source data is older than 10 minutes or recomputation is queued When serving API responses Then return 200 with stale=true, lastComputedAt from the previous computation, and include Retry-After: 60 And no hard failures (5xx) are emitted solely due to missing or stale inputs
Coordinated Rotation Across Series
"As an organizer, I want the system to coordinate rotations across multiple series so that no one person consistently gets the short end of the stick."
Description

Create an optimization routine that proposes and schedules upcoming meeting times across all linked series to keep each participant’s cumulative off-hour impact within a configurable tolerance band. Respect hard constraints (work hours, holidays, focus blocks, required attendee presence) and support soft preferences with weights. Provide preview and commit flows, swap suggestions when conflicts arise, freeze windows to avoid late changes, and partial attendance handling. Upon commit, write back updates to connected calendars, ensure idempotency, and notify stakeholders of changes and rationale.

Acceptance Criteria
Fairness Optimization Across Linked Series
Given linked series with defined participants, personal working hours, holidays, focus blocks, required/optional roles, and a configured off-hour tolerance band When the optimizer generates the next N scheduled instances across all linked series Then each required participant’s cumulative off-hour minutes across the generated instances falls within the tolerance band, or the system flags infeasible participants with explicit reasons And off-hour minutes are computed per participant relative to their local time zone and working hours And the proposal includes per-participant totals and deltas versus the current schedule And if multiple feasible schedules satisfy the tolerance band, the system selects the one that minimizes the maximum individual off-hour minutes (minimax), then minimizes total off-hour minutes as a secondary tie-breaker
Enforcement of Hard Scheduling Constraints
Given defined hard constraints for each participant and series (working hours, holidays, focus blocks, and required attendee presence) When generating proposed times Then no proposed instance violates any hard constraint And if no feasible time exists for an instance, the system marks the instance as unschedulable, lists the blocking constraints, and excludes it from commit unless manually resolved
Weighted Soft Preferences Influence
Given soft preferences with numeric weights (e.g., preferred windows, series-level target hours, individual morning/afternoon bias) When optimizing across feasible candidate times Then the selected schedule maximizes the total weighted preference score among feasible solutions And the preview displays the total score and per-preference contributions for transparency And changing a weight by Δ produces a recomputed proposal that reflects the updated weights deterministically
Preview, Commit, Idempotency, and Calendar Writeback
Given a generated proposal in preview state When the user commits the proposal Then connected calendars are created/updated with consistent event IDs per instance and attendee, with no duplicate events And repeating the same commit request with the same proposal produces no additional changes (idempotent) And the commit is atomic per series; on any write failure within a series, no updates from that series are persisted and the user is shown actionable errors And stakeholders receive notifications summarizing changes, rationale (fairness and preference scores), and affected instances And an audit log records who committed, what changed, timestamps, and external calendar operation results
Swap Suggestions and Conflict Resolution
Given a conflict where at least one required attendee cannot attend a proposed instance within constraints When generating alternatives Then the system produces up to K ranked swap suggestions across upcoming instances (configurable horizon), each satisfying hard constraints and not worsening any required participant beyond the tolerance band And each suggestion includes a rationale with fairness deltas and preference score impact And accepting a swap updates the preview and recalculates impacts in under T seconds
Freeze Windows and Late-Change Protection
Given a configured freeze window duration (e.g., 72 hours before start time) When generating proposals Then no changes are proposed to instances starting within the freeze window unless an explicit override is set by an authorized user And the preview labels freeze-protected instances and reports the number of deferred changes And commits reject modifications to freeze-protected instances without override and display a clear error
Partial Attendance and Required Presence Compliance
Given series rules allowing optional attendees and requiring specific roles to be present When proposing times Then all required attendees for each instance are schedulable at the proposed time within hard constraints And optional attendees outside their working hours are not counted toward tolerance band fairness metrics but their off-hour impact is reported separately And the preview and calendar writebacks mark optional attendees with appropriate invitation status and attendance notes
Availability & Policy Ingestion
"As an operations manager, I want the system to honor each person’s availability and our org policies so that scheduling decisions are accurate and respectful."
Description

Ingest and normalize availability signals and policies from connected calendars and org settings, including working hours, focus blocks, PTO, and regional holidays. Support per-user preferences, team-level defaults, and group-level overrides for balance groups. Keep data fresh within a defined latency budget, reconcile conflicts between sources, and provide privacy controls to respect visibility scopes. Expose a consolidated availability model to the Fairness Score Engine and scheduler.

Acceptance Criteria
Calendar Signal Ingestion and Normalization
Given a user has connected Google or Microsoft 365 calendars with configured working hours, focus blocks, and OOO/PTO, and their region is set, When an initial full sync is executed, Then 100% of working hours, focus blocks, OOO/PTO, and regional holidays within the next 90 days are present in the normalized availability model with correct local time conversion and types [WORK_HOURS, FOCUS_BLOCK, PTO, HOLIDAY]. Given delta changes (create/update/delete) occur for any of the supported signals, When the delta processor runs, Then the corresponding normalized records are created/updated/soft-deleted with source_id and last_synced_at populated. Given duplicate or overlapping source events representing the same block, When normalization occurs, Then the model de-duplicates by source event id and merges contiguous blocks of the same type.
Team Defaults and User Preference Merge
Given team defaults define working hours 09:00–17:00 and no user preference exists, When consolidation runs, Then the user's effective working hours are 09:00–17:00. Given a user sets working hours 10:00–18:00, When consolidation runs, Then user preference overrides team default and the effective hours are 10:00–18:00. Given org policy enforces a maximum daily meeting span of 10 hours, When a user preference would exceed that span, Then the effective hours are clipped to comply with org policy and a policy_applied flag is recorded.
Group-Level Overrides for Balance Groups
Given a balance group "APAC-US" defines an override window 07:00–19:00 for Wednesdays, When a user in that group with base hours 09:00–17:00 requests consolidated availability for that group and day, Then the effective window is 07:00–19:00 and the override source is recorded as BALANCE_GROUP. Given the same user requests consolidated availability without specifying a balance group, When consolidation runs, Then the effective window reverts to 09:00–17:00. Given a group override conflicts with an org hard block (e.g., company holiday), When consolidation runs, Then the hard block wins and the time is unavailable.
Latency Budget Freshness
Given a supported signal changes in a connected calendar and a webhook is received, When normalization completes, Then the normalized model reflects the change within 60 seconds at p95 and within 120 seconds at p99 for active tenants. Given initial connection to a calendar with up to 5,000 relevant events in the next 180 days, When the initial backfill starts, Then it completes within 10 minutes and exposes progressive readiness via a sync_status endpoint. Given regional holiday data updates at the provider, When the daily refresh job runs, Then the normalized model is updated within 24 hours.
Conflict Reconciliation Rules
Rule: Precedence order is ORG_POLICY_HARD_BLOCK > PTO > HOLIDAY > FOCUS_BLOCK > WORK_HOURS > TEAM_DEFAULT. Given overlapping PTO and focus blocks, When consolidation runs, Then the time is marked UNAVAILABLE with reason PTO. Given overlapping HOLIDAY and WORK_HOURS where org policy treats holidays as soft, When consolidation runs, Then the time is marked SOFT_UNAVAILABLE with reason HOLIDAY and remains schedulable only if all participants consent.
Privacy Controls and Visibility Scopes
Given a requester without "view details" permission for a user, When querying consolidated availability, Then event titles and descriptions are never exposed and block types are redacted to BUSY/SOFT_BUSY per policy. Given a user marks focus blocks as Private, When consolidation runs, Then the exported model shows SOFT_BUSY with no "focus" label for non-privileged requesters, and FULL type for the owner. Given an admin sets a visibility scope to "availability only", When the API is called, Then only time spans and availability flags are returned with no source metadata.
Consolidated Availability API for Fairness Engine and Scheduler
Given the Fairness Score Engine requests availability for a user list, time range, and optional balance group, When the /availability/v1/query endpoint is called, Then the response returns 200 with a list of availability windows including fields [user_id, start, end, availability_state, reason, source, version] for all inputs. Given the same request parameters are sent twice without underlying changes, When called with If-None-Match ETag, Then the API returns 304 Not Modified. Given an invalid balance group id is supplied, When the API is called, Then the response is 400 with a machine-readable error code INVALID_BALANCE_GROUP.
Overrides & Conflict Resolution
"As an organizer, I want to override specific meetings with clear audit trails so that urgent needs can be met without losing long-term fairness."
Description

Provide a workflow for manual overrides when automatic balancing cannot satisfy all constraints. Allow organizers to set one-time exceptions, lock specific occurrences, and capture reasons for auditability. Generate next-best suggestions with quantified fairness trade-offs, and trigger automatic rebalancing of future instances to compensate where possible. Maintain an immutable audit log, support rollback, and send targeted notifications to affected attendees.

Acceptance Criteria
Create One-Time Override with Reason
Given I am an organizer with edit permission on a linked series group When I open occurrence O and set a one-time time override and enter a reason of at least 10 characters Then the override is saved, the occurrence time updates, a non-editable "Override" marker appears on O, and an audit log record is created capturing user, timestamp, old time, new time, reason, and override_id And affected attendees with outside-work-hour impact are flagged in the UI with counts And the save completes within 1 second for series <= 100 occurrences Given I attempt to save an override without a reason or with fewer than 10 characters Then the save is blocked and an inline validation error is shown Given I attempt to override a past occurrence (end time < now) Then the action is blocked with an explanatory message
Lock a Specific Occurrence
Given I am an organizer When I lock occurrence O in a linked series Then O is excluded from all subsequent auto-balancing operations, a lock icon is displayed, and an audit log entry is recorded with lock metadata Given an auto-rebalance job runs for the series Then O's time remains unchanged Given I perform a bulk shift/rebalance across the series Then O is skipped with a "skipped: locked" note in the summary report Given I unlock O Then the lock is removed, a new audit entry is created, and O becomes eligible for future rebalancing
Generate Next-Best Suggestions with Fairness Scores
Given an occurrence cannot satisfy all constraints under current settings When I request suggestions Then the system returns up to 5 suggestions within 2 seconds, each with start/end time, projected per-attendee off-hours minutes, a fairness_score (0–100), and a delta_vs_current summary And suggestions honor locked occurrences, attendee holidays, and work hours unless I explicitly enable override mode And suggestions are sorted by highest fairness_score; ties are broken by smallest total off-hours minutes Given fewer than 3 feasible options exist Then the response includes all feasible options plus reason codes for infeasible windows Given no feasible options exist Then the system returns a "no feasible suggestions" message with top blocking constraints listed
Auto-Rebalance Future Instances After Override
Given an override increases attendee A's off-hours burden When auto-rebalance is triggered for the linked series group (immediately after save or on the next scheduled run) Then a compensation plan is generated within 5 seconds for series <= 100 occurrences that reduces A's cumulative off-hours over the next 4 upcoming occurrences without changing any locked occurrences And the plan shows before/after fairness scores and per-attendee off-hours deltas And no attendee's cumulative off-hours increases by more than 15 minutes unless no feasible plan exists; if exceeded, the plan is flagged and requires organizer confirmation Given no compensating moves are possible under constraints Then the system records an explanation and marks the series as "needs manual attention" without making changes
Immutable Audit Log Entry and Export
Given any override, lock/unlock, suggestion acceptance, or rollback is saved Then an append-only audit entry is created with a monotonic sequence id, actor id, action type, timestamp (UTC), affected occurrence ids, old/new values, reason (if provided), and a cryptographic hash linking to the previous entry And audit entries cannot be edited or deleted; attempts return 403 and are themselves logged And organizers and org admins can view and export audit entries to CSV for a selected date range; attendees cannot view reasons And the export includes the hash chain so integrity can be verified offline
Rollback an Override and Recompute
Given an override exists on occurrence O When I select Rollback and confirm Then O reverts to its pre-override schedule if that slot is still available and not locked, a rollback audit entry is created referencing the original override_id, and auto-rebalance is re-run for future instances And affected attendees are re-notified of the change Given the original slot now conflicts with a lock or another immovable constraint Then the rollback is blocked with an explanation and no changes are applied Given a rollback is invoked multiple times for the same override Then subsequent rollbacks are treated as idempotent no-ops and return a success message with no changes
Targeted Notifications to Affected Attendees
Given an override, lock/unlock, suggestion acceptance, or rollback changes the time of one or more occurrences When the change is saved Then only attendees whose personal schedule is impacted receive notifications via enabled channels (email, in-app, Slack if connected) within 2 minutes And notifications include before/after times in the recipient's local timezone, the stated reason (if provided), an .ics update, and a link to view details And notifications respect recipient preferences and quiet hours; if within quiet hours, delivery is deferred unless the meeting starts within 12 hours, in which case an urgent summary is sent And multiple changes within 5 minutes are deduplicated into a single consolidated notification per recipient And delivery failures are surfaced to the organizer with retry options
Fairness Insights & Alerts
"As a team lead, I want visibility and alerts on fairness trends so that I can proactively adjust schedules and policies before issues escalate."
Description

Deliver a dashboard and exports that visualize fairness over time across balance groups, including cumulative off-hour minutes per participant, upcoming risk hot spots, and forecasted impact under current rotations. Provide alerts via email/Slack when thresholds are breached, weekly digests summarizing fairness status, and CSV/JSON exports for leadership reporting. Surface actionable recommendations to reduce imbalance (e.g., adjust weights, expand acceptable windows).

Acceptance Criteria
Dashboard: Cumulative Off-Hour Minutes by Participant
Given I have Viewer access to a balance group and select a date range When I open the Fairness Insights dashboard Then I see each participant with cumulative off-hour minutes for the selected range computed against their configured work hours, holidays, and focus blocks And canceled or declined occurrences are excluded from all totals And overlapping meetings count minutes only once per participant And partial overlaps are counted to the minute based on actual overlap outside acceptable hours And the time-series view displays off-hour minutes aggregated by week within the range And totals in the table match the sum of underlying occurrences within the selected filters
Dashboard: Upcoming Risk Hot Spots
Given breach thresholds are configured for a balance group When the system forecasts a participant will exceed the threshold within the next 4 weeks based on scheduled meetings and current rotations Then a Risk Hot Spot is displayed showing participant, affected week, projected off-hour minutes, threshold, and delta And the hot spot includes a deep link to recommendations to mitigate the risk And when the forecast no longer exceeds the threshold, the hot spot is removed on the next refresh
Forecast: Impact Under Current Rotations
Given a balance group with linked series and active rotations When I open the Forecast view and select a horizon (default 8 weeks) Then the system computes projected off-hour minutes per participant for each week within the horizon using current rotation rules and scheduled events And editing rotation order or participant weights updates the forecast immediately for the same horizon And the forecast clearly labels the date range and assumptions used
Alerts: Threshold Breach via Email and Slack
Given email and Slack destinations and alert thresholds are configured for a balance group When a participant's off-hour minutes exceed the configured threshold within the defined window Then an alert is sent via email and Slack containing balance group, participant, metric, threshold, time window, and a deep link to the dashboard And duplicate alerts for the same participant, group, and threshold window are suppressed for 24 hours And alerts respect user notification preferences for channel and frequency And undeliverable alerts are logged with an error reason visible to admins
Weekly Digest: Fairness Summary
Given weekly digests are enabled and recipients are configured When the scheduled digest time occurs Then recipients receive a summary of the prior week including per-balance-group totals, per-participant off-hour minutes, groups over threshold, upcoming hot spots, and top recommendations And the digest is delivered via email and Slack with consistent content And the digest indicates the applied filters and date range And links in the digest open the dashboard with matching filters
Exports: CSV and JSON for Leadership Reporting
Given I have Viewer access, select a date range, and choose one or more balance groups When I export CSV from the dashboard Then the file downloads with headers: group_id, group_name, participant_id, participant_name, date_range_start, date_range_end, off_hour_minutes_total, on_hour_minutes_total, meetings_count And numeric values are integers in minutes and dates are ISO 8601 in UTC And totals match the dashboard for the same filters When I request the JSON export via API with the same filters Then the response returns the same fields plus pagination (limit, offset, total) with HTTP 200 And unauthorized API requests return HTTP 401 and no data
Recommendations: Actions to Reduce Imbalance
Given a balance group exceeds the defined imbalance tolerance When I view the Recommendations panel Then the system proposes actionable changes (e.g., adjust participant weights, expand acceptable windows, shift meeting start times, or re-sequence rotations) with an estimated reduction in off-hour minutes And each recommendation shows affected participants, required change, projected improvement, and confidence level And I can Apply a recommendation to create the corresponding configuration change or Dismiss with a reason And applied recommendations update the forecast and dashboard immediately

Swap Market

Let attendees propose one-off slot trades within fairness constraints, with automatic recalculation of debt/credit. Approvals are one click and the ledger updates instantly, keeping the long-term rotation plan intact while accommodating real-life conflicts.

Requirements

One-off Swap Proposal Composer
"As an attendee, I want to quickly propose a swap for a conflict slot from the timeline so that I can resolve my schedule without doing time zone math or messaging back-and-forth."
Description

Provide an in-product flow to propose a one-time slot trade directly from the draggable timeline and event detail panes. The composer lets an attendee pick a requested slot, select one or more offered slots to trade, add an optional note, and preview validity against work hours, holidays, and focus blocks in real time. Hard guardrails prevent proposing swaps that violate team policies or create after-hours assignments. The UI surfaces affected participants, local times across regions, and the fairness impact before submission. Proposals are packaged into a lightweight object that can be shared via smart link or sent as an in-app notification, integrating seamlessly with Timeglue’s scheduling and timeline painting model.

Acceptance Criteria
Launch Composer from Timeline and Event Detail
Given I have a slot selected on the draggable timeline, When I click "Propose swap" from the slot context menu or press the assigned shortcut, Then the Swap Proposal Composer opens and the requested slot is preselected. Given I am viewing an event's detail pane, When I click "Propose swap", Then the composer opens with that event's slot preselected as the requested slot. Then the composer loads team policy, user profile, and timezone context and renders initial validation within 500 ms. And the composer opens as a modal or side panel without navigating away, preserving the underlying timeline state. And if I close and reopen within the same session, Then my in-progress selections and note are restored.
Select Requested and Offered Slots
Given the composer is open with a requested slot preselected, When I modify the requested slot by clicking another eligible slot on the timeline, Then the requested slot updates and prior offered selections clear. Given the composer is open, When I select one or more offered slots to trade (from my eligible future slots for the same rotation), Then each selection appears in an "Offered" list with local time and date labels. Then the "Submit proposal" action remains disabled until at least one offered slot is selected. And offered slots cannot overlap my confirmed events or violate soft/hard constraints per team policy; ineligible slots are visually disabled with a tooltip reason. And the optional note field accepts up to 500 characters and supports plain text only; characters remaining are displayed and validation prevents overflow.
Real-time Validity Preview Against Policies
Given I add or remove requested/offered slots, When the selection changes, Then per-slot validity badges update within 300 ms to one of: Valid, Warning (soft constraint), or Blocked (hard constraint), with a primary reason shown. Then the composer displays a summary banner of overall proposal validity with counts of Valid/Warning/Blocked slots. And hovering a badge reveals all applicable policy checks (work hours, holidays, focus blocks) evaluated in my local time and the counterparties' local times.
Hard Guardrails Prevent Invalid Proposals
Given any selected requested or offered slot is Blocked by a hard rule (e.g., outside configured work hours or on a blocked holiday), When I attempt to submit, Then submission is prevented and the first blocking reason is focused and announced to screen readers. And slots that would assign after-hours work to any affected participant are not selectable; attempts to select show a non-dismissive tooltip stating the policy violated. And the "Submit proposal" button is enabled only when all selected slots are Valid or Warning and no Blocked slots remain.
Participants, Local Times, and Fairness Impact Preview
Given a valid selection exists, Then the composer lists all affected participants (requester and potential counterparties) with their local times for each requested and offered slot. Then a fairness impact panel shows the projected debt/credit delta per participant and overall rotation neutrality (sum delta = 0), with values rounded to two decimals. And fairness and participant data refresh within 300 ms after any selection change.
Proposal Packaging and Share
Given I have a valid proposal, When I click "Submit proposal", Then a proposal object is created containing: proposal_id, requester_id, requested_slot_id, offered_slot_ids[], note (optional), affected_participant_ids[], fairness_delta_summary, policy_snapshot_id, created_at, expires_at. Then the serialized proposal payload is ≤ 5 KB and is stored and retrievable by proposal_id. And a smart link is generated and copied to clipboard within 100 ms; opening the link shows a proposal preview with the same selection and validity state snapshot. And in-app notifications are sent to targeted counterparties within 5 seconds with deep links to approve/decline; the composer confirms dispatch success or shows an actionable error.
Fairness Constraints Engine
"As a team lead, I want swaps to be checked against clear fairness rules so that no one carries an unfair after-hours burden over time."
Description

Implement a policy-driven engine that evaluates swap eligibility and computes fairness impact. The engine enforces configurable rules such as maximum weekly after-hours burden, rolling-window credit/debit caps, holiday weighting, time-zone offset severity scoring, and blackout periods. It returns a pass/fail decision with human-readable reasons and a normalized fairness delta used to update the ledger. Policies are team-configurable by admins and evaluated consistently during proposal, approval, and reversion. The service is stateless, versioned for auditability, and exposes APIs for the UI, notifications, and rotation planner.

Acceptance Criteria
Enforce Max Weekly After-Hours Burden
Given a team policy defines maxAfterHoursPerWeek and weekDefinition, and participants have working hours and time zones configured And a proposed swap schedules some portion outside a participant’s working hours within the policy’s weekDefinition When the engine evaluates the swap Then it returns decision=fail with reason.code=MAX_AFTER_HOURS_EXCEEDED when projectedAfterHours > maxAfterHoursPerWeek And reason.details includes participantId, limitHours, currentAfterHoursHours, projectedAfterHoursHours, weekStart, weekEnd And if projectedAfterHours <= maxAfterHoursPerWeek, it returns decision=pass and includes the after-hours contribution in delta.normalized
Enforce Rolling-Window Credit/Debit Caps
Given a policy defines rollingWindowDays, debitCap, and creditCap, and the current ledger balances for each participant When the engine projects balances including the proposed swap over the rollingWindowDays Then if projectedDebit > debitCap or projectedCredit > creditCap for any participant, it returns decision=fail with reason.code=ROLLING_CAP_EXCEEDED And reason.details includes participantId, windowStart, windowEnd, currentDebit, projectedDebit, currentCredit, projectedCredit, capType, capValue And if all projected balances are within caps, it returns decision=pass and provides delta.normalized reflecting the swap’s fairness impact
Apply Holiday Weighting and Offset Severity Scoring
Given a policy defines holiday calendars, holidayWeightMultiplier, disallowOnObservedHolidays, and an offsetSeverity scoring schema with thresholds And the proposed slot intersects an observed holiday for any participant and/or exceeds an offsetSeverity threshold When the engine evaluates the swap Then if disallowOnObservedHolidays=true for any impacted participant, it returns decision=fail with reason.code=HOLIDAY_BLACKOUT and reason.details includes participantId and holidayId/date And otherwise it applies the holidayWeightMultiplier to the affected burden and includes reason.code=HOLIDAY_WEIGHT_APPLIED with multiplier in details And it applies offset severity according to the policy schema, includes reason.code=OFFSET_SEVERITY_APPLIED with severityTier and numeric adjustment, and adjusts delta.normalized accordingly
Respect Blackout Periods During Swap Evaluation
Given team-level and participant-level blackoutPeriods with IDs and time ranges are configured in policy When the proposed slot overlaps any blackout period for any participant Then the engine returns decision=fail with reason.code=BLACKOUT_CONFLICT and details include blackoutId, participantId (if applicable), and conflictingTimeRange And if there is no overlap, evaluation proceeds to other rules without a blackout failure
Return Decision, Reasons, and Normalized Fairness Delta
Given a valid evaluation request including participants, proposedSlot, ledgerSnapshot, and an optional policyVersion When the engine evaluates the request Then it responds with decision in {pass, fail}, reasons[] each with code (machine-readable), message (human-readable), and details (structured), and delta.normalized as a float in [-1.0, 1.0] And delta.normalized is present and non-zero only when decision=pass, and is deterministic for identical inputs And reasons[].message is concise (<=200 chars) and explains each triggered rule in plain language
Policy Versioning and Auditability
Given policies are versioned and updates create a new immutable policyVersion When an evaluation specifies a policyVersion, that exact version is used; when unspecified, the current active version is used Then the response includes policyVersion and evaluationId, and re-evaluating with the same inputs and policyVersion returns identical outputs And during approval and reversion of the same proposal, the engine uses the original policyVersion pinned to the proposal for consistent outcomes
Stateless, Lifecycle-Consistent Evaluation Across APIs
Given the same evaluation inputs are sent via UI, notifications, and rotation planner API integrations When the engine evaluates during proposal, approval, and reversion phases Then all endpoints return identical decisions, reasons, and delta.normalized for the same inputs and policyVersion And the engine maintains no server-side session state and produces the negative of the original delta.normalized when evaluating a reversion of a previously applied swap
Real-time Credit/Debit Ledger
"As a team member, I want a transparent ledger of credits and debits so that I can see how swaps affect my long-term fairness balance."
Description

Create a per-member fairness ledger that tracks credits and debits resulting from completed swaps, expirations, and reversions. The ledger supports weighted scoring by slot severity (e.g., late-night, weekend, holiday), rolling windows, and configurable decay. It recalculates instantly when a proposal is accepted or withdrawn, and exposes current balance, recent transactions, and reasons to both individuals and admins for transparency. The ledger integrates with the rotation planner to keep long-term plans intact while reflecting one-off trades.

Acceptance Criteria
Instant Recalculation on Swap Acceptance or Withdrawal
Given a pending swap proposal between Member A and Member B And both members have current ledger balances When the proposal is accepted by an authorized approver Then the server recalculates both balances within 250 ms and persists updated balances atomically with the swap state And a single transaction is appended per affected member with fields: event_id, reason=swap_accepted, severity_weight, delta, balance_before, balance_after And the UI/API for both members reflects the new balances within 1 second of acceptance When the proposal is withdrawn before acceptance Then no ledger transaction is recorded and balances remain unchanged
Accurate Entries for Completed Swaps, Expirations, and Reversions
Given an event occurs of type swap_completed, swap_expired, or swap_reverted When the event is processed Then ledger entries are created per affected member with type matching the event and contain: member_id, timestamp_utc, slot_id, severity_label(s), severity_weight, delta, balance_before, balance_after, event_id And processing is idempotent: reprocessing the same event_id yields no duplicate entries and no balance change And for swap_reverted, the net effect across the related entries equals the negative of the original swap_completed delta after applying current decay rules And the member's current_balance equals the decayed sum of transactions within the active rolling window
Severity Weighting by Slot Type
Given severity weights are configured by admin for labels: standard (default 1.0), late_night, weekend, holiday And a swap involves a slot tagged with one or more severity labels When calculating the ledger delta Then the effective weight is computed per configured combination policy (max by default) and documented in the transaction And delta = base_unit_value (default 1.0) * effective_weight with sign set to credit for taking a worse slot and debit for giving up a worse slot And unit tests cover at least: single label, overlapping labels, unknown label (uses 1.0), and policy override to additive or multiplicative
Rolling Window and Configurable Decay
Given an admin configures a rolling window length (e.g., 90 days) and a decay function (none|exponential with half-life H|weekly factor F) When computing current_balance Then only transactions with timestamp within the window contribute after applying decay; outside the window contribute 0 And changing window or decay causes balances to be recomputed deterministically; for up to 10,000 transactions per workspace, recomputation completes within 5 seconds And balances for a fixed configuration are reproducible: repeated computations yield identical results And the API exposes per-member raw_sum, decayed_sum, window_start, and window_end
Transparency and Access Controls for Individuals and Admins
Given an authenticated individual requests their ledger via GET /ledger/me Then the API responds within 500 ms (95th percentile) with fields: current_balance, last_updated_at, and the 50 most recent transactions with reason, severity, delta, balance_after And the response contains only the requester’s data; attempting to access another member without admin role returns 403 Given an authenticated admin requests GET /ledger/{member_id} Then the API allows pagination of full history and includes admin-only annotations if present And all GET responses reflect the latest committed balances with staleness not exceeding 1 second from last event processing
Rotation Planner Integrity with One-off Trades
Given a long-term rotation plan exists for a team When a one-off swap is accepted Then the baseline rotation plan remains unchanged; only the specific instance assignment reflects the swap And future planned assignments are not auto-shifted by the ledger update When the swap is reverted Then the original assignment is restored for the instance and corresponding ledger entries adjust accordingly And after any swap or reversion, no member has duplicate or missing assignments for the affected instance
Concurrency, Ordering, and Auditability
Given two or more swap events affecting the same member are accepted within a short interval When the ledger processes the events concurrently Then updates are serialized per member by sequence number and final balances are deterministic based on event_id ordering And each ledger entry has a monotonically increasing per-member sequence and is immutable And an export endpoint allows admins to download an audit log (CSV or JSON) for a date range including all entry fields and verification hashes And attempts to modify existing entries are rejected; adjustments must be appended as new entries with reason=adjustment and a link_to prior event_id
One-click Approval & Auto-Apply
"As an attendee, I want to approve a proposed swap in one click so that the event and fairness balance update instantly without manual steps."
Description

Enable recipients (and optional approvers) to approve or decline a swap in one click from in-app notifications or emails. Upon approval, the system atomically updates the event ownership/assignment, applies fairness deltas to the ledger, and updates all affected calendars. Approvals support expiry windows, dual consent, optional manager overrides, and auto-approval when policies permit. The workflow includes optimistic locking and conflict handling to prevent race conditions, and emits webhooks for downstream integrations.

Acceptance Criteria
In-app and email one-click approve applies swap atomically
Given a pending swap request with a valid approval token and the recipient is authorized to approve When the recipient clicks Approve from an in-app notification or email link Then the system atomically reassigns the event ownership/assignment per the swap, applies the fairness ledger delta exactly once, and queues calendar syncs And the swap status becomes Approved with approver identity and timestamp recorded and an audit log entry is created And confirmation notifications are sent to all impacted parties And external calendars connected by either party reflect the change within 60 seconds And repeat clicks of the same Approve link are idempotent and do not create duplicate ledger entries And the approver receives a success response within 2 seconds
One-click decline from notification or email cancels swap request
Given a pending swap request with a valid action token When the recipient clicks Decline from an in-app notification or email link Then the swap status becomes Declined with an optional reason captured and an audit log entry is created And no changes are made to event ownership/assignment, calendars, or the fairness ledger And notifications are sent to the requester and any watchers And repeat clicks of the same Decline link are idempotent
Approval expiry window enforcement
Given a swap request with an expiry timestamp and a still-pending status When an approval or decline link is clicked after the expiry time Then the system marks the swap as Expired, blocks approval, and leaves event ownership/assignment and the ledger unchanged And the user sees an Expired message with a call-to-action to propose a new swap And notifications are sent to involved parties about expiry And clicking within the expiry window proceeds according to normal approval/decline rules
Dual consent approval workflow
Given a team policy requiring dual consent for swaps between two attendees When the first attendee approves the swap Then the swap transitions to Pending Second Consent with the original expiry window preserved and no changes applied to ownership, ledger, or calendars When the second attendee approves within the expiry window Then the system applies the swap atomically, updates the ledger exactly once, updates calendars, sets status to Approved, and sends confirmations When the second attendee declines or the request expires Then the swap is marked Declined or Expired and no changes are applied
Auto-approval and manager override policy enforcement
Given a policy that auto-approves swaps meeting predefined criteria (e.g., fairness delta within threshold, no focus-block violations, within work hours) When a qualifying swap is submitted Then the system approves it without human action, applies the atomic changes, updates calendars, updates the ledger, sets status to Approved, sends notifications, and records an audit log entry Given a policy enabling manager overrides When an authorized manager approves a pending swap (including those awaiting second consent) or re-approves a previously declined swap within the policy-defined override window Then the system applies the swap immediately, marks the approval source as Manager Override, enforces fairness delta application, updates calendars, and records the override in the audit log And policy violations (e.g., exceeding max debt threshold) are blocked unless the override policy explicitly permits them
Optimistic locking and conflict handling
Given two or more approval or decline actions for the same swap are submitted concurrently or near-simultaneously When the system processes these requests Then exactly one outcome commits based on the first successful transaction and all others receive a conflict response indicating the final state And no partial updates occur: event ownership/assignment, ledger, and calendar updates are either fully applied or not applied And retrying with the same idempotency key yields the already-committed result without side effects And audit logs capture all attempts with outcomes
Webhook emission for downstream integrations
Given webhooks are configured for the workspace When a swap is approved, declined, or expired Then the system emits a corresponding webhook event with a signed payload including swap id, actors, previous/updated assignment, ledger delta, status, and timestamps And delivery is at-least-once with exponential backoff retries for up to 24 hours, and a dead-letter notification is created on repeated failures And event order is preserved per swap id And webhook failures do not affect the transactional outcome of the swap
Calendar Sync & Conflict Guard
"As a scheduler, I want approved swaps to sync reliably to our calendars and avoid conflicts so that no one ends up double-booked or out of hours."
Description

Integrate swap outcomes with connected calendars (Google Workspace, Microsoft 365) to update invitations, ownership, and attendee lists while preventing double-booking. On approval, the system releases the original slot, holds the new slot, and commits changes only if both calendars confirm. It respects privacy settings, maintains ICS consistency for external guests, and backfills failed syncs via retry queues. Pre-approval validation checks for conflicts across work hours, focus blocks, and existing events to avoid creating overlaps.

Acceptance Criteria
Transactional Calendar Commit on Swap Approval
Given a swap is approved by all required parties and both users' calendars are connected When the system processes the swap Then it creates a tentative hold on the target slot in both calendars within 3 seconds And marks the original slot as pending release And commits ownership, attendees, and time changes only after receiving success confirmations from all affected calendars And if any provider fails or exceeds a 15-second commit timeout, all changes are rolled back and the original slot is restored And the operation is idempotent using a stable swap ID so repeated callbacks or retries do not create duplicate events
Pre-Approval Conflict Guard Across Work Hours, Focus Blocks, and Events
Given a proposed swap between two users When pre-approval validation runs Then the swap is rejected if the new slot falls outside either user's configured work hours And the swap is rejected if the new slot overlaps either user's focus blocks And the swap is rejected if the new slot overlaps existing accepted or tentative events for either user And the swap is approved only if no conflicts exist for both users And the response includes a reason code for rejections (OUTSIDE_WORK_HOURS, FOCUS_BLOCK_CONFLICT, EVENT_CONFLICT) And validation completes within 2 seconds for the 95th percentile
Double-Booking Prevention During Pending Approval and Commit
Given a swap proposal is pending or in commit phase When the system places a hold on the target slot Then the connected calendars reflect the slot as Busy/Tentative within 3 seconds And Timeglue blocks creation of overlapping events via its scheduling APIs with HTTP 409 Conflict And if an overlapping event is added directly to the calendar during hold, the swap is auto-aborted prior to commit with reason CALENDAR_CONFLICT and the hold is cleared within 5 seconds And if the swap is rejected or rolled back, all holds are removed within 5 seconds
Privacy-Respecting Event Sync
Given users have privacy settings (Private, Title-Only, Full) When syncing swap outcomes to connected calendars Then event visibility matches the user's setting for viewers outside the event: Private shows Busy only; Title-Only exposes title only; Full exposes all fields And sensitive fields (description, location, attachments, guest list) are omitted where privacy requires And no more information than configured is exposed on either provider And audit logs record which fields were written per provider without storing redacted content
Invitation, Organizer, and Attendee List Updates
Given a swap is successfully committed When syncing to calendars Then the new owner appears as the organizer/host where supported; otherwise the event is safely re-created with the new organizer and prior invitees preserved And attendee lists reflect the swap: swapped-in attendee added; swapped-out attendee removed And prior attendees' RSVP statuses are preserved where provider supports; otherwise statuses are re-requested And provider notification emails are issued according to provider rules and only once per affected attendee
ICS Consistency for External Guests
Given the event includes external guests via ICS When a swap update is synced Then the outgoing ICS retains the same UID and increments SEQUENCE for updates with METHOD:REQUEST And DTSTART/DTEND, ORGANIZER, and ATTENDEE fields reflect the new state And external guests receive a single update and do not see duplicate events in Apple Calendar, Outlook, or Gmail And if the swap is rolled back or canceled, METHOD:CANCEL is sent with the correct UID and SEQUENCE
Retry and Backfill on Provider Failures
Given a provider API call fails transiently (HTTP 429/5xx) or times out When syncing swap changes Then the change is enqueued with exponential backoff retries up to 8 attempts or 24 hours, whichever comes first And retries are idempotent via swap ID and provider event IDs to avoid duplicates And successful backfill updates the calendars and clears the queue entry And hard failures (HTTP 4xx) mark the sync as Failed and notify the user with actionable remediation guidance
Swap Audit Trail & Revert
"As an admin, I want a complete audit of swaps and the ability to revert mistakes so that we can maintain trust and compliance."
Description

Maintain an immutable audit log for the lifecycle of each swap, including proposals, validations, approvals, declines, expirations, and ledger adjustments. Provide admin tools to revert a swap with a reason, automatically reversing ledger effects and restoring calendar state when possible. The audit trail is filterable, exportable (CSV/JSON), and redacts sensitive details per role. Event-sourced records ensure traceability for compliance and allow reconstruction of historical fairness balances.

Acceptance Criteria
Immutable Audit Log for Swap Lifecycle Events
Given a new swap proposal P When it is submitted Then an audit event with type="proposal.created", swap_id=P.id, actor_id, event_id, created_at (UTC ISO-8601), and seq is appended and visible within 1 second Given validations run for P When validation completes Then an event of type in {"validation.passed","validation.failed"} is appended with validator_id and reasons (if failed), and no prior event is modified Given an approval, decline, or expiration occurs for P When the action is recorded Then an event of type in {"approval.granted","approval.declined","proposal.expired"} is appended and the event sequence remains strictly increasing per swap Given any attempt to edit or delete an existing audit event via UI or API When the attempt is made Then the request is rejected with 403 and no change occurs to the event count, event payloads, or per-swap checksum Given concurrent actions on the same swap When multiple events are appended Then seq increases by exactly 1 per new event and events are returned in seq order for that swap
Event Schema Completeness per Lifecycle Stage
Given an event of type "proposal.created" When persisted Then it must include {swap_id, actor_id, proposed_slot.start, proposed_slot.end, previous_slot.start (nullable), previous_slot.end (nullable), rationale_redacted, created_at, seq, schema_version}; otherwise the write is rejected with 400 and no side effects Given an event of type "validation.failed" When persisted Then it must include {swap_id, validator_id, reasons[non-empty], ruleset_id, created_at, seq, schema_version}; otherwise reject with 400 and no side effects Given an event of type "approval.granted" When persisted Then it must include {swap_id, approver_id, method in ["manual","auto"], ledger_delta, pre_balance, post_balance, created_at, seq, schema_version} Given an event of type "approval.declined" When persisted Then it must include {swap_id, decliner_id, reason, ledger_delta=0, created_at, seq, schema_version} Given an event of type "proposal.expired" When persisted Then it must include {swap_id, expired_at, timeout_seconds, ledger_delta=0, created_at, seq, schema_version} Given an event of type "ledger.adjusted" When persisted Then it must include {swap_id (nullable), subject_user_id, rule_id, delta, pre_balance, post_balance, cause_event_id, created_at, seq, schema_version} Given an event of type "calendar.changed" When persisted Then it must include {swap_id, action in ["create","update","delete","restore"], calendar_event_ids[], results.status per id, created_at, seq, schema_version} Given any event missing required fields or with invalid types When validation runs Then the event is rejected with 400, an "event.write.rejected" audit entry is logged with error_code, and swap state is unchanged
Admin-Initiated Swap Revert with Ledger and Calendar Restoration
Given a swap S that was approved with a non-zero ledger_delta and calendar changes When an admin with permission "swap.revert" clicks Revert and provides a non-empty reason Then a "revert.started" event is appended with {admin_id, reason, created_at} and compensating actions begin Given ledger effects from S When the revert executes Then a compensating "ledger.adjusted" event is appended such that all affected users' balances equal their pre-approval balances Given calendar entries created or modified by S When the revert executes Then those entries are restored to their pre-swap state where possible and results are captured in a "calendar.changed" event; any failures are listed in a "revert.partial" event with affected ids and error_codes Given revert processing completes When finalization occurs Then a "revert.completed" event is appended with outcome in {"full","partial","noop"} and references to cause_event_ids Given the same revert command is retried within 24 hours for S When executed Then the operation is idempotent: no duplicate compensations occur and a single "revert.duplicate" event is appended Given the audit log for S When inspected after revert Then all original events remain unchanged and only new revert/compensation events are appended
Role-Based Redaction in Audit Views and Exports
Given a viewer with role in {"member","guest"} When viewing audit events for a swap Then fields {private_notes, ip_address, email, external_calendar_ids, access_token, requester_contact} are redacted as "[redacted]" and a redaction_reason is present per field Given a viewer with role in {"admin","compliance"} When viewing or exporting audit events Then no redaction is applied unless legal_hold=true, in which case redaction follows the hold policy and is indicated per field Given a non-admin initiates an export When the file is generated Then the same redaction level as their UI view is applied; record counts and checksums reflect the redacted content Given API access with token scoped to "audit:read" When requesting raw events Then redaction is enforced per role; attempts to request unredacted fields without scope are rejected with 403 and are logged Given internal logs for request processing When written Then they must not include unredacted sensitive payloads for non-privileged viewers; automated checks flag violations as failures
Filter, Sort, and Paginate Audit Trail
Given filters {date_from/date_to, swap_id, user_id, event_type[], outcome[], ledger_delta_min/max} When applied to the audit API Then the response contains only matching events and includes filter_echo for traceability Given sort by created_at in {asc, desc} When requested Then ordering is by created_at with seq as a tiebreaker within the same timestamp Given pagination with cursor and page_size<=200 When requesting successive pages Then no events are duplicated or skipped across pages and next_cursor is provided until exhaustion; total_count is included when result set <=10,000 Given a dataset of >=100,000 events and a filtered query returning <=200 rows When executed under nominal load Then p95 response time is <=1,000 ms and p99 is <=1,800 ms Given invalid filter values or unsupported combinations When requested Then the API returns 400 with error_code and details; no partial data is returned
Export Audit Trail to CSV and JSON with Timestamps and Checksums
Given an applied filter in the audit UI When the user selects Export CSV Then a UTF-8 CSV with RFC4180 quoting, comma delimiter, header row, and ISO-8601 UTC timestamps is generated with columns: {event_id, swap_id, event_type, created_at, actor_id, approver_id, decliner_id, validator_id, ledger_delta, pre_balance, post_balance, redaction_applied, redaction_reason, metadata_hash} Given the user selects Export JSON When generated Then a JSON array containing the same records and fields is produced; numeric types are preserved; no truncation occurs Given export size >10,000 records When export is requested Then processing runs asynchronously; a status endpoint is available; completion email is sent; the download URL expires in 24 hours; a SHA-256 checksum is provided and matches the file content Given export is initiated by a non-admin When generated Then redactions are applied identical to UI; admin/compliance roles may toggle include_sensitive=true which is logged in an "export.requested" audit event Given an export fails due to an error When failure occurs Then an "export.failed" audit event is recorded with error_code and correlation_id; the user sees a retriable error
Historical Fairness Balance Reconstruction from Event Store
Given a user U and timepoint T When the system replays all ledger-related and swap lifecycle events up to and including T Then the computed fairness balance for U equals the stored snapshot at or before T (or equals ground truth if no snapshot exists); tolerance = 0 Given a revert event exists for a swap affecting U When events are replayed Then compensating ledger.adjusted events ensure the balance after T reflects the pre-swap state Given divergence between replayed balance and stored balance (absolute difference > 0) When detected Then an "integrity.anomaly" event is logged, the API returns 409 with a correlation_id, and the anomaly is visible in the admin integrity dashboard Given upcaster rules for schema_version changes When replaying events of mixed versions Then upcasters normalize events to the current model; failures halt replay with an error and log an "upcast.failed" event Given <=10,000 events for U over 1 year When replay is executed under nominal load Then p95 replay time is <=2 seconds and memory usage remains below 200 MB
Smart Swap Suggestions
"As an attendee, I want the system to suggest fair swap options so that I can resolve conflicts quickly without searching everyone’s schedules."
Description

Offer ranked suggestions for viable counter-slots and participants based on fairness relief, availability within work hours, timezone fit, and policy compliance. When a user selects a conflict slot, the system computes candidate trades, scores them, and presents a short list with predicted ledger impact and local-time previews. Supports filters (e.g., exclude holidays, prefer same-day) and gracefully degrades when limited options exist. The service is designed to support future multi-party chains but initially limits to two-party swaps for simplicity.

Acceptance Criteria
Ranked suggestions with fairness and policy scoring
Given a user selects a conflict slot of defined duration And there exist multiple eligible participants and counter-slots When the system computes suggestions Then it returns a ranked list limited to the top 5 items by default And each item includes: participant name, counter-slot start/end in requester’s local time and counterpart’s local time, predicted ledger delta, and policy compliance badge And suggestions that violate mandatory policies (e.g., max daily hours, required roles, fairness debt ceiling) are excluded And the ranking prioritizes higher fairness relief, within-work-hours fit, timezone overlap, and policy compliance in that order And ties are broken deterministically by earliest counter-slot start time then participant ID
Work-hours, focus blocks, and timezone fit enforcement
Given organizational work-hour calendars, user focus blocks, and existing events When generating candidate counter-slots for a selected conflict slot Then any slot outside either party’s defined work hours is excluded And any slot overlapping either party’s focus blocks or confirmed events is excluded And any slot shorter than the required meeting duration is excluded And remaining slots must provide at least 80% overlap with both parties’ work hours for the meeting duration
Ledger impact preview accuracy
Given current ledger balances for both parties When the system presents a suggested swap Then it displays the predicted ledger delta for each party with a +/− sign and unit (credits) And the delta equals (post-swap projected balance − current balance) computed using the active fairness policy And the sum of deltas across both parties equals zero for two-party swaps And hovering or tapping the delta reveals the components used in the calculation
Filter controls: exclude holidays and prefer same-day
Given the suggestions panel shows filter controls When the user toggles Exclude holidays ON Then the list recomputes within 1 second and removes any slots on either party’s holiday calendars When the user toggles Exclude holidays OFF Then holiday slots may appear and are labeled with a Holiday indicator When the user enables Prefer same-day Then same-calendar-day options for the requester receive a deterministic rank boost without excluding other days And the active filter state persists across page reloads and shared smart links
Graceful degradation with limited or no options
Given fewer than three viable candidates exist When the system displays suggestions Then it shows all available candidates with a Limited options notice and guidance to adjust filters When zero candidates exist Then the UI displays an Empty state with actions: Expand hours, Allow next-day, Notify organizer And the API responds 200 with an empty list and a reason code indicating why no candidates were found
Two-party swap limit enforcement
Given the system is limited to two-party swaps When computing suggestions Then it does not propose any multi-party chain suggestions And if an API request specifies more than two participants, it is rejected with error code SWAP-CHAIN-NOT-SUPPORTED and a human-readable message And an audit log entry records the rejection with requester ID, timestamp, and participants list
Performance and freshness of suggestions
Given a conflict slot is selected When requesting suggestions Then the backend returns results within 800 ms at p95 and 1500 ms at p99 And results reflect calendar and ledger data no older than 5 seconds at the time of computation And if source calendars change while the panel is open, a Refresh available indicator appears within 10 seconds

Legal Limits

Build locale labor rules and rest-period requirements into rotations, blocking suggestions that risk non-compliance. FairShare surfaces safe alternates and explains the rule that applied, giving leads confidence that fairness never violates policy or law.

Requirements

Locale Rule Library & Versioning
"As a compliance-conscious team lead, I want a trusted library of locale labor rules with versioning so that our schedules always reflect current legal requirements."
Description

Centralized, extensible repository of jurisdiction-specific labor constraints (maximum daily/weekly hours, mandatory rest windows, night work limits, on-call recovery, weekend rules) with effective dates and version history. Rules are scoped by country, state/province, and union/contract where applicable, and mapped to each participant’s employment locale in Timeglue. Supports rule composition, precedence, and exceptions, and ships with curated defaults plus admin-defined custom rules. Integrates with Timeglue’s working hours, holidays, and focus blocks to compute combined constraints, and exposes a rules catalog and metadata (IDs, citations, summaries) for downstream enforcement and explainability.

Acceptance Criteria
Curated Defaults by Locale Available
Given a new Timeglue workspace with no custom rules and the default Rule Library installed When an admin filters the Rule Library by country=DE and state=BY with union/contract=None Then the library lists curated default rules scoped to DE-BY with unique rule_ids, citations, and summaries And at least one rule exists for each category: maximum daily hours, maximum weekly hours, mandatory rest windows, night work limits, weekend rules, on-call recovery And each listed rule displays its effective_from and effective_to dates and current status in {Active, Expired, Scheduled}
Admin Creates Custom Rule With Effective Dates
Given an admin defines a custom Mandatory Rest Window rule scoped to country=US, state=CA, union=Teamsters-Local-123 with effective_from=2025-10-01 and effective_to=2027-09-30 And the rule body sets min_rest_hours=12h with a citation URL and summary When the admin saves the rule Then the rule is created with a unique rule_id and version=1 And the rule appears in the Rule Library with the specified scope and date range And the rule is applied only on/after 2025-10-01 and not before And saving is blocked with a validation error if effective_to < effective_from or units are invalid
Automatic Mapping to Participant Employment Locale
Given a participant has employment locale country=FR, state=IDF, union=None mapped in their profile And the participant is included in a rotation When the scheduler computes constraints for that participant Then rules scoped to FR-IDF and FR (country-level) are applied And rules from other locales are not applied And if the participant locale mapping is missing, scheduling is blocked and a clear error prompts assignment of employment locale
Precedence, Composition, and Exceptions Resolution
Given a participant has applicable rules: country-level weekly_max=48h, state-level weekly_max=44h, union override weekly_max=40h, and an exception allowing weekly_max=42h for project X from 2025-09-01 to 2025-12-31 When constraints are resolved for a week within 2025-10-01 Then precedence is exception > union > state > country, producing an effective weekly_max=42h And outside the exception date range the effective weekly_max=40h And a resolution trace is available identifying all considered rules, their scopes, and the winning rule with reasons
Rule Versioning and Audit History
Given an existing custom rule rule_id=R-123 version=1 Active with effective_from=2025-10-01 When an admin edits the rule to change min_rest_hours to 11h effective_from=2026-01-01 Then a new immutable version=2 is created with the updated fields and effective_from=2026-01-01 And version=1 remains read-only and continues to apply before 2026-01-01 And the audit log records editor identity, timestamp, and a field-level diff And scheduling uses version=1 for dates before 2026-01-01 and version=2 on/after that date
Combined Constraints Integration with Hours, Holidays, Focus Blocks
Given two participants in different time zones with employment locales set and Timeglue working hours, focus blocks, and local holidays configured When the lead requests suggested meeting windows for a date range Then returned windows satisfy all applicable locale rules for each participant And windows do not intersect any participant’s off-hours, holidays, or focus blocks And any rejected window includes at least one explanation reference with rule_id or constraint_id and reason
Rules Catalog Exposure and Explainability
Given an API client with read access calls GET /rules?scope=CA-BC When the request is processed Then the response includes an array of rules with fields: rule_id, scope, title, summary, citation, effective_from, effective_to, categories, version, status And the API supports pagination via limit/offset and ETag for caching And when the scheduler flags a violation, the explain endpoint returns violating rule_id(s), scope, and a human-readable explanation including citations
Real-time Rule Enforcement
"As a scheduler, I want non-compliant times to be automatically blocked as I plan so that I don’t accidentally create illegal or risky schedules."
Description

Deterministic rule engine that evaluates proposed meetings and rotations against the active rule set and blocks non-compliant suggestions in real time. Hooks into the draggable timeline, smart link suggestions, and rotation generators to validate rest periods, hour caps, and protected time before slots are displayed or confirmed. Provides consistent evaluation across UI and API, handles time zone conversions and DST, and returns structured violation payloads (rule ID, severity, interval, participants) for UI messaging and logging.

Acceptance Criteria
Timeline Drag Blocks Non-Compliant Slots
Given an active rule set (minRest=12h, maxDailyHours=8h, protectedTime=18:00–08:00 local) and two participants in different locales When a user drags to propose a meeting that violates any active rule for any participant Then the slot becomes non-selectable and is visually indicated as blocked within 100ms of drag stop And the confirm action is disabled for the blocked slot And a violation summary is shown referencing the blocking ruleId and severity And compliant slots in view remain selectable
Smart Link Suggestion Filter Honors Legal Limits
Given a smart link request for participants A and B with the active rule set When the suggestions API is called to fetch candidate windows Then all returned suggestion windows are compliant for all participants And no non-compliant window is returned And if alternates are requested, at least 3 nearest compliant windows within ±72h are returned when available And for any excluded window, the API can return a violations array containing at least one violation with ruleId and severity
Rotation Generation Enforces Caps and Rest Periods
Given an 8-week rotation template, participant hour caps (≤40h/week), minRest=11h, and locale-specific protected times and holidays When the rotation generator produces assignments Then no participant exceeds their weekly hour cap And no assignment violates minRest between the end of one assignment and the start of the next And assignments do not overlap protected time or local holidays And any removed or shifted assignment emits a violation with ruleId, severity, interval, and participants And the generator returns a fully compliant schedule or a failure with violations and no schedule applied
UI and API Decision Consistency and Determinism
Given an identical input payload (participants, ruleSetVersion, candidate intervals) When evaluated via the UI timeline service and via the public API v1 Then the allow/deny decision for each candidate interval is identical across channels And the set and ordering of violations (by ruleId and interval) are identical And repeated evaluations with the same inputs produce the same results and a stable decisionHash And results include ruleSetVersion and evaluationVersion metadata
Cross-Time-Zone and DST Accurate Evaluation
Given participant A in America/New_York and participant B in Europe/Berlin with the active rule set When evaluating the slot 2025-03-09T06:30:00Z–07:30:00Z (US DST start) Then A's local times are computed as 01:30–03:30 with actual elapsed duration of 1 hour, and B's local times as 07:30–08:30 CET And rest periods and hour caps are evaluated using each participant's local calendar and actual elapsed time across DST changes And no false allow/deny occurs due to DST transition And all violation intervals are returned as timezone-aware ISO 8601 timestamps
Violation Payload Schema and Content
Given any non-compliant evaluation of a candidate slot or assignment When the engine returns violations Then each violation includes ruleId (string), severity (error|warning), interval {start, end, timezone}, and participants [{participantId, timezone}] And start and end are valid ISO 8601 with offsets, timezone is an IANA TZ identifier, and values are non-null And the participants array lists all impacted participants for the violation And the payload is consistent across UI and API responses
Real-Time Evaluation Performance Thresholds
Given a rule set of up to 15 rules and up to 10 participants When evaluating 500 candidate slots on standard production hardware Then per-slot evaluation latency is ≤50ms at p95 and ≤100ms at p99 And UI block/allow feedback after drag stop appears ≤150ms And the suggestions API sustains ≥1000 evaluations/second with error rate <0.1% over a 5-minute window
Safe Alternate Suggestions (FairShare)
"As a team lead, I want the system to offer fair, compliant alternatives when a slot is blocked so that I can reschedule quickly without violating policy."
Description

When a candidate slot is blocked, the system computes and ranks compliant alternates that preserve fairness across participants and respect work hours, holidays, and focus blocks. Uses a constraint solver to search adjacent windows and rotation offsets, balancing equity metrics (time-of-day distribution, off-hours burden) while guaranteeing legal compliance. Integrates into the timeline and smart links, presenting up to N best options with minimal disruption to existing plans.

Acceptance Criteria
Compliant Alternates for Blocked Slot
Given a candidate meeting slot is blocked for any participant by legal limits, work hours, holidays, or focus blocks and N>0 with search window W hours and rotation offset limit R When FairShare computes alternates Then it returns M suggestions where 1 ≤ M ≤ N that: - satisfy all applicable locale labor rules and required rest periods for every participant - respect each participant’s configured work hours, holidays, and focus blocks - avoid conflicts with existing confirmed events on participant calendars - fall within ±W hours of the original start or use rotation offsets ≤ R And all returned suggestions are unique by start time and participant set
Fairness-Aware Ranking and Determinism
Given workspace fairness weights (w_time_of_day, w_off_hours) and a fairness score F computed for each alternate When alternates are produced Then the list is strictly sorted by descending F And ties are broken by smaller disruption_delta (absolute minutes from original), then earlier start time (UTC) And the top-1 suggestion has F ≥ F(original) unless no compliant alternate exists And identical inputs (including solver seed) produce identical ranking order
Rule Explanation and Compliance Audit
Given any suggested alternate Then the suggestion includes: reason_code for the original block, legal_rule_id(s) and policy_version evaluated, compliance_status="pass", and a plain-language explanation ≤ 200 characters And timestamps in the explanation are rendered in each participant’s locale time zone and include UTC offsets And an audit log entry is recorded capturing rule_id(s), policy_version, solver seed, and timestamp
Timeline and Smart Link Integration
Given the schedule timeline view or a shared smart link with a blocked slot When alternates are available Then up to N suggestions are displayed inline beneath the blocked slot with start time, participant count, fairness_delta, and a compliance badge And selecting an alternate and confirming Replace atomically updates the schedule, replacing the original slot across all participant calendars and updating the smart link And the replacement is undoable for 30 seconds And viewing alternates via smart link requires schedule_edit permission; otherwise alternates are hidden
Performance and Graceful Degradation
Given ≤ 20 participants, calendars spanning ≤ 8 weeks, and ≤ 50 candidate windows per participant When computing alternates Then p95 end-to-end latency ≤ 1200 ms and p99 ≤ 2000 ms And if the solver reaches a 1500 ms budget, it returns best-so-far results with partial=true and diagnostics.solver_timeout=true And peak memory usage during computation ≤ 200 MB
No Viable Alternates Messaging and API Response
Given no compliant alternates exist within configured search bounds (W, R) When alternates are requested Then the UI displays an empty state listing the top 3 blocking rules and offers controls to expand W or adjust R or rotation participants And the API responds 200 with suggestions=[], diagnostics.violated_rule_ids (ranked counts), and hints including expand_window and rotate_owner
Rule Explainability & Traceback
"As a manager, I want clear explanations of blocked times so that I understand the rule and can adjust the schedule confidently."
Description

Human-readable explanations that show which rule triggered, why it applied, and how to resolve, including jurisdiction, effective dates, citation, and impacted intervals. The UI highlights the conflicted span on the timeline, displays the rule summary and next compliant windows, and supports localization. The API returns machine-readable details for integrations, enabling audit trails and user notifications.

Acceptance Criteria
UI Explanation for Blocked Meeting Suggestion
Given an organizer proposes a meeting that violates a Legal Limits rule And the rule engine flags the suggestion as non-compliant with a rule_id When the organizer opens the suggestion details panel Then the conflicted span on the timeline is highlighted for all affected participants And the rule summary is displayed including rule name, jurisdiction code/name, effective_from and effective_until, and a citation link And the impacted intervals are listed with start and end in both each participant’s local time and ISO 8601 UTC And the explanation states why the rule applied in plain language (e.g., "violates required rest period") And at least 3 next compliant windows are shown, ordered by earliest start, with local times per participant And choosing an alternate window replaces the original suggestion and clears the violation indicator
API Traceback Payload for Non-Compliance
Given a client calls POST /v1/schedule/evaluate with a proposed window that is non-compliant When the evaluation completes Then the response includes violations[] entries each containing: rule_id, rule_name, rule_version, jurisdiction_code, jurisdiction_name, effective_from, effective_until, citation_url, reason_code, reason_i18n_key, reason_text, severity, blocking=true, impacted_intervals[{participant_id,start,end,tz}], evaluated_at, correlation_id And alternates[] contains 3–10 compliant windows with {start,end,score,rationale_i18n_key} And schema_version is present and content-type is application/json; charset=utf-8 And all timestamps are ISO 8601 with timezone offsets and second-level precision And violations are sorted by severity desc then start asc And p95 latency for requests with ≤100 participants is ≤400ms
Localized Rule Explanation Display
Given the user’s language is set to a supported locale (e.g., fr-FR or ar-SA) When a non-compliance explanation is shown in the UI Then all user-facing strings (titles, reasons, actions, tooltips) are localized to the selected language And date/time values render per locale conventions, including 12/24h and week start And jurisdiction names are localized where available; codes remain unchanged And right-to-left locales mirror layout appropriately, including the timeline and panels And if a translation is missing, the UI falls back to en-US without placeholder artifacts And ARIA labels and descriptions are localized And the API supplies i18n keys and arguments enabling client-side translation parity
Audit Logging of Rule Evaluations
Given workspace audit logging is enabled When a schedule evaluation yields one or more violations or the explanation panel is opened Then an audit event is recorded with: correlation_id, actor_id (or system), workspace_id, meeting_id, rule_id, rule_version, jurisdiction_code, decision (blocked|warning), impacted_interval_count, earliest_start, latest_end, alternates_count, created_at, client_metadata (UA/IP hashed) And PII (names/emails) is excluded or hashed per policy And the event is available via GET /v1/audit/events?correlation_id=… within 5 seconds And events are retained ≥13 months and exportable as NDJSON And access to audit endpoints enforces authorization; unauthorized requests receive 403
Accessible Timeline Highlight and Explanation
Given a keyboard-only user navigates to a blocked suggestion When focus moves to the conflicted interval or explanation panel Then all interactive elements are reachable via keyboard with visible focus indicators And the highlighted interval has a contrast ratio ≥4.5:1 against its background And screen readers announce the rule name and the interval start/end in the user’s locale And tooltips/dialogs with explanations are operable without hover and have appropriate ARIA roles and labels And no accessibility violations of WCAG 2.1 AA are detected by automated checks on the modal/panel
Safe Alternate Windows Generation
Given a proposed meeting violates a rest-period or labor rule for at least one participant And participants have configured work hours, holidays, and focus blocks When alternates are generated Then 3–10 compliant windows within the next 14 days are produced And no alternate violates any participant’s legal limits, work hours, holidays, or focus blocks And alternates are sorted by earliest start, then lowest inconvenience score And each alternate displays a short rationale (e.g., "after rest period ends") And selecting an alternate updates the proposal and removes the violation badge
Jurisdiction and Effective Date Resolution
Given a participant is subject to multiple overlapping rules (e.g., federal and state) or a mid-period jurisdiction change When evaluating a proposed time that could fall under different rule versions Then the engine selects the applicable jurisdiction and rule version based on precedence and the event time And the UI explanation cites the applied jurisdiction, rule version, effective dates, and precedence reason And the API response includes evaluation_path steps indicating how jurisdiction and version were determined And if ambiguity remains, a disambiguation notice links to workspace policy settings
Audited Admin Overrides
"As an admin, I want a documented override process so that rare exceptions can be handled responsibly and auditable for compliance."
Description

Controlled workflow for authorized admins to request one-time or bounded overrides with mandatory justification, configurable approval steps, and risk acknowledgment. Overrides are time-limited, granular to participant and rule, and never bypass critical hard-stop rules where prohibited. All actions are recorded with timestamps, actors, and diffs, with exportable logs and alerts to compliance stakeholders.

Acceptance Criteria
Submit Time-Bounded, Granular Override With Mandatory Justification
Given an authorized admin with override permissions And a selected participant set and specific compliance rule(s) to override And a defined time window with start and end timestamps (UTC) within the organization’s maximum override duration And a justification provided with a minimum length of 15 non-whitespace characters When the admin submits the override request as either one-time (single scheduling instance) or a bounded window Then the system validates permissions, time bounds, participant and rule scope, and justification length And rejects the request with specific inline errors if any required element is missing or invalid And persists a pending override request with a unique ID, status "Pending Approval", and the exact requested scope
Configurable Multi-Step Approval Workflow With SLAs and Escalation
Given an override request is pending approval and the organization policy requires N sequential approvals with defined approver roles And the requestor is not permitted to approve their own request When approvers act in sequence within the configured SLA (e.g., 24 hours per step) Then the system records each approval/decline with timestamp, actor, and comment And auto-escalates to a fallback approver if an SLA is exceeded And auto-expires the request if the final approval is not received by the overall deadline, setting status to "Expired" And prevents approval actions by unauthorized users or out-of-order steps
Hard-Stop Rules Are Non-Overridable
Given a compliance rule is flagged as a hard stop (non-overridable) in policy configuration When an admin attempts to include that rule in an override request Then the system blocks submission and displays an error referencing the rule ID/name and policy source And does not create any override request record And ensures scheduling suggestions remain governed by the hard-stop rule without change
Risk Acknowledgment Required Prior to Submission
Given the override form includes a computed risk summary (affected participants, rules impacted, time outside compliance, and policy references) When the admin reviews the summary Then the submit action remains disabled until the admin explicitly checks the risk acknowledgment checkbox And upon submission, the system records the acknowledgment actor, timestamp, and risk summary snapshot with policy version And rejects submissions if the acknowledgment is missing or revoked before submit
Audit Logging With Timestamps, Actors, Diffs, and Export
Given auditing is enabled for overrides When any lifecycle event occurs (create, update, approve, decline, escalate, execute, expire, revoke, export) Then an immutable audit entry is appended capturing ISO-8601 UTC timestamp, actor ID, actor role, event type, request ID, source IP/agent, and before/after diff of relevant fields And audit entries are viewable only to roles with audit permissions And exports can be generated in CSV and JSON with filters by date range, actor, request ID, status, and event type And each export is itself logged with who exported, when, and applied filters
Compliance Stakeholder Alerts on Key Events
Given compliance stakeholders and channels (email and webhooks) are configured When an override request is submitted, approved, declined, executed, expired, or revoked Then the system sends alerts to all configured channels within 2 minutes of the event And includes request ID, actor, event type, impacted participants, rule scope, time window, and a link to details And retries failed deliveries with exponential backoff up to 5 attempts and flags undeliverable alerts for follow-up
Auto-Expiration and Safe Reversion of Overrides
Given an approved override with a defined end timestamp When the end timestamp is reached Then the override status transitions to "Expired" automatically and an audit entry is recorded And future scheduling suggestions immediately resume applying the original compliance rules without the override And the system sends an expiration alert to compliance stakeholders And no residual override effects persist beyond the end timestamp
Locale & Team Assignment Management
"As an operations owner, I want to manage each user’s legal locale and exceptions so that the right rules apply automatically during scheduling."
Description

Administration tools and APIs to assign employment locale and legal profile to users and teams, including hierarchy defaults and per-user exceptions. Supports HRIS import and periodic sync, travel/temporary assignments with start/end dates, and resolution rules when locale differs from current timezone. Ensures enforcement aligns with employment contracts while maintaining correct time zone math in Timeglue.

Acceptance Criteria
Team Default Locale & Legal Profile Assignment
Given a team has a configured default employment locale and legal profile And a new user is added to the team without explicit per-user settings When the user record is created or the user is assigned to the team Then the user’s employment locale and legal profile are set to the team defaults within 1 minute And the assignment source is recorded as "Team Default" with timestamp and actor And the values are visible in the admin UI and in GET /users/{id} responses And scheduling components read these values for legal rule evaluation
Per-User Exception Overrides Team Default
Given a team has default locale and legal profile And an admin sets a per-user employment locale and/or legal profile override When the override is saved Then the user’s effective employment locale/legal profile reflect the override immediately And the assignment source shows "User Override" with timestamp and actor And subsequent changes to the team default do not change the user’s overridden values And removing the override reverts the user to the team default within 1 minute
HRIS Initial Import of Locale & Legal Profile
Given the HRIS connector is configured with field mappings for employment locale and legal profile When a full import is executed Then existing users are updated and new users are created with mapped locale/profile values And values not in the supported code lists are rejected with error entries and prior values remain unchanged And per-user overrides are not overwritten unless the mapping is flagged authoritative=true And running the same import twice without HRIS changes results in zero additional updates (idempotent) And an import report lists counts of created, updated, unchanged, and errored records
Source Precedence & Resolution Rules
Given a user may have values from multiple sources (Team Default, HRIS, User Override, Temporary Travel Assignment) When conflicting values exist concurrently Then the effective locale/legal profile resolve by priority: Temporary Travel Assignment (if defines a temporary legal profile) > User Override > HRIS (authoritative) > Team Default And the effective source and value are displayed in the admin UI and returned by GET /users/{id} with fields effectiveSource and effectiveValues And changing or removing a higher-priority source immediately recalculates the effective values
Temporary Travel Assignment with Start/End Dates
Given an admin schedules a temporary travel assignment with start and end datetimes, a travel timezone, and optional temporary locale/legal profile When current time reaches the start datetime Then the user’s effective timezone switches to the travel timezone for time calculations And if a temporary legal profile is provided, legal rule evaluation uses it; otherwise it continues to use the employment locale’s legal profile And when current time passes the end datetime, the user reverts to prior timezone and legal profile immediately And the system prevents saving overlapping travel assignments for the same user and returns a validation error
Enforcement When Employment Locale Differs From Current Timezone
Given a user’s current timezone differs from their employment locale/legal profile When the system generates meeting suggestions or validates proposed times Then legal limits and rest-period rules are evaluated using the user’s employment locale/legal profile And all time math (e.g., working hours windows) uses the user’s current timezone And non-compliant suggestions are blocked and an explanation cites the applied rule and source profile And compliant alternates are suggested within the next 5 seconds

Fairness Simulator

Before you lock a recurring series, simulate the next quarter’s rotations and see projected fairness scores by person and region. Tweak cadence, duration, or weights and watch equity and compliance outcomes update instantly, so you ship a plan everyone can live with.

Requirements

Rotation Simulation Engine & Fairness Scoring
"As a remote team lead, I want to simulate the next quarter’s rotations with fairness scores so that I can validate a recurring plan is equitable across people and regions before I finalize it."
Description

Implement a deterministic simulation engine that expands recurring-meeting rules (cadence, duration, start anchors) over the next quarter to produce projected occurrences per participant and region while honoring Timeglue constraints (work hours, holidays, focus blocks, and existing calendar conflicts). Compute multi-dimensional fairness metrics, including distribution of early/normal/late local-time bands, after-hours exposure, rotation balance by person and region, adherence to target participation weights, maximum consecutive unfavorable slots, and skip-gap limits. Support configurable weights per person and region, hard vs. soft constraints with penalty scoring, tie-break strategies, and seedable randomness for reproducibility. Output a normalized 0–100 fairness score with detailed per-entity breakdowns, violations, and recommendations, available to the UI and via internal service APIs.

Acceptance Criteria
Deterministic Recurrence Expansion Over Next Quarter
Given a weekly cadence on Tuesday 15:00 UTC, 60-minute duration, and start anchor 2025-09-02T15:00:00Z with a 13-occurrence quarter horizon When the simulation runs with participants assigned IANA time zones Then exactly 13 occurrences are produced with ISO 8601 timestamps aligned to Tuesdays 15:00:00Z Given work hours, holidays, focus blocks, and existing calendar conflicts for each participant When assignments are produced Then no hard constraint is violated in the final assignment And any soft constraint overlaps are recorded as penalties with minute-accurate durations Given identical inputs and seed "abc123" When the simulation runs twice Then the occurrences and assignments are byte-identical across runs Given DST transitions for any participant during the horizon When local-time bands are computed Then band classification uses the correct offset in effect on the occurrence date
Hard vs. Soft Constraints and Penalty Application
Given configuration where work hours and regional holidays are hard, and focus blocks are soft with penalty weight 2.0 per hour When the simulation schedules occurrences Then no meeting is scheduled outside any participant's work hours or on their regional holiday And any focus block overlap accrues a penalty equal to overlapMinutes × (2.0/60) Given at least one occurrence with no feasible assignment that satisfies all hard constraints When overrideMode=false Then the engine returns an infeasibility error containing occurrenceId and blockingConstraintIds And no schedule is produced Given the same inputs with overrideMode=true When the simulation runs Then the engine selects the minimal-penalty assignment by softening the fewest higher-priority constraints first And each softened hard constraint is flagged as "hard-overridden" in violations with severity=critical Given the output When penalties are aggregated Then the per-entity penalty totals equal the sum of individual violation penalties within ±0.0001
Multi-Dimensional Fairness Scoring and Normalization
Given band definitions early [05:00–08:59], normal [09:00–17:59], late [18:00–22:59], after-hours [23:00–04:59] in participant local time When scoring Then counts and minutes per band are computed for each participant and region Given global metric weights {timeBands:0.25, afterHours:0.25, rotationBalance:0.20, weightAdherence:0.20, sequenceLimits:0.10} When the overall fairness is computed Then the normalized score S is in [0,100] and increases monotonically when penalties decrease Given targets: CV of unfavorable slots per person ≤0.25, after-hours minutes per person = 0, maxConsecutiveUnfavorable ≤2, skipGap ≤3 When the schedule is evaluated Then any violation is listed with occurrenceIds and contributes to penalties Given adherence tolerance of ±2 percentage points for participation weights When achieved vs. target shares are compared Then deviations within tolerance incur zero penalty And deviations beyond tolerance incur linear penalties proportional to the excess Given the final output When S ≥ 90 Then the recommendations list may be empty When S < 90 Then at least one actionable recommendation is included per affected entity
Per-Person and Per-Region Weight Adherence
Given per-person participation weights summing to 1.0 and per-region weights summing to 1.0 When the simulation completes Then achieved participation proportions per person and per region are within ±2 percentage points of targets or the deviation is recorded as a penalty equal to excessPercentagePoints × configuredWeightFactor Given a participant with weight 0 When assignments are generated Then that participant is never assigned to attend And their after-hours exposure equals 0 minutes Given the user updates weights and reruns the simulation with the same seed When only weights change Then occurrences remain identical And assignments and scores update deterministically and return within 300 ms P95 for 13 occurrences and up to 50 participants
Tie-Break Strategy and Seedable Randomness
Given tie-break order [leastRecentUnfavorable, lowestAfterHoursMinutes, lowestTotalAssignments, alphabeticalByName] When multiple candidates are equally eligible Then the engine selects the candidate according to this order Given the same inputs and seed "tg-seed-42" When the simulation runs multiple times Then all tie-break decisions are identical across runs And changing the seed yields potentially different tie-break resolutions while remaining constraint-compliant Given audit logging enabled When a tie-break occurs Then the decision log includes occurrenceId, consideredCandidates[], chosenCandidateId, appliedTieBreakSteps[], and seed
API Contract for Simulation Results
Given a completed simulation When GET /internal/simulations/{id} is called Then response status is 200 and Content-Type is application/json When the response body is validated Then it conforms to the published JSON Schema version "v1" including fields: id (string), inputs (object), seed (string), occurrences (array), assignments (array), fairness (object with overallScore [0–100]), breakdowns (per person and region), violations (array), recommendations (array) Given an occurrence object When inspected Then it contains id, start (ISO 8601 UTC), end (ISO 8601 UTC), participantIds (array), timeBandsByParticipant (map), flags {afterHours:boolean,dstTransition:boolean} Given a 13-week weekly series with 12 participants When the API returns Then payload size is ≤ 1.5 MB and P95 latency ≤ 700 ms on production hardware
Performance and Scalability for Instant Simulation
Given weekly cadence with 13 occurrences and 50 participants across 5 regions When simulation runs on a standard production instance Then end-to-end compute time (expand + assign + score) is ≤ 1.5 s P95 and ≤ 2.5 s P99 And peak memory ≤ 512 MB Given up to 52 occurrences and 100 participants When simulation runs Then end-to-end compute time is ≤ 3.0 s P95 and ≤ 4.5 s P99 And peak memory ≤ 1 GB Given only weights or tie-break order changed When recomputing Then incremental recomputation completes in ≤ 300 ms P95 and ≤ 600 ms P99 for 13 occurrences Given concurrent requests of 10 parallel simulations with the above parameters When executed Then the system maintains P95 latencies within the stated bounds and no request fails
Real-time What‑if Controls
"As a scheduler, I want to adjust cadence, duration, and weights and see results update instantly so that I can quickly converge on a fair plan without trial-and-error in calendar tools."
Description

Provide interactive controls (sliders, inputs, and toggles) to tweak cadence (weekly/biweekly/monthly), meeting duration, anchor timezone, allowed windows, participant/region weights, and constraint strictness with instant recomputation of outcomes. Use debounced, incremental recompute to keep updates sub-second, show loading states and confidence deltas, and preserve UI responsiveness. Include preset templates (Follow the Sun, Region Rotation, Weight by Role), undo/redo of parameter changes, and reset-to-baseline. Persist the current parameter set in the URL for sharable, reproducible simulations.

Acceptance Criteria
Sub-second Debounced Recompute and Loading States
Given the Fairness Simulator is loaded with a baseline parameter set and a dataset of up to 50 participants over a 13-week horizon When the user adjusts any single parameter via slider, input, or toggle Then the recomputation starts within 50 ms, a loading indicator displays if computation exceeds 250 ms, and all outcome panels and charts refresh within 500 ms at P95 (≤1000 ms at P99) Given the user continuously drags a slider or types rapidly (≥10 updates/sec) When updates occur Then at most one recompute is triggered every 300 ms (debounced), and the final value triggers a recompute within 300 ms of input idle Given outcomes are recomputing When UI remains interactive Then input latency stays ≤100 ms and frame rate remains ≥50 FPS on a reference device (Chrome, latest, laptop with 4-core CPU) Given outcomes have refreshed When comparing to previous and baseline states Then confidence deltas display absolute values and ± changes for key metrics (fairness, compliance, coverage) with correct sign and 2-decimal precision
Cadence and Duration Controls Update Outcomes
Given the cadence control is set to Weekly by default When the user switches cadence to Biweekly or Monthly Then the projected schedule horizon and rotation points update accordingly and fairness/compliance metrics recompute within the sub-second targets Given meeting duration input allows 15-minute increments between 15 and 180 minutes When the user sets a value within range Then the duration is applied, utilization and fairness metrics update, and the value is persisted in the parameter state Given the user types an out-of-range or non-numeric duration When the field loses focus or debounce commits Then validation prevents commit, shows an inline error message, and reverts to the last valid value
Anchor Timezone and Allowed Windows Controls
Given the anchor timezone is currently set (e.g., UTC) When the user changes the anchor timezone (e.g., to PST) Then displayed timelines, rotation anchors, and computed meeting windows shift to the new timezone and outcomes recompute within sub-second targets Given allowed windows are represented on a draggable timeline with 15-minute granularity When the user drags or resizes a window segment Then the new window bounds are applied immediately to constraints and reflected in outcomes, with at most one recompute per 300 ms during drag and a final recompute on release Given keyboard accessibility When the user focuses a window handle and presses Arrow keys with Shift modifiers Then the window moves/resizes in 15-minute increments and updates outcomes accordingly
Weights and Constraint Strictness Adjust Equity and Compliance
Given participant and region weights accept values from 0.1 to 5.0 with 0.1 steps When the user increases a participant’s weight Then that participant’s share of favorable slots increases and fairness scores reflect the new weighting within the next recompute Given constraint strictness has modes Soft and Hard When the user switches to Hard and the current configuration is infeasible Then an Infeasible configuration banner appears with guidance to relax constraints, and the last feasible schedule is retained for display Given constraint strictness is Soft When strict constraints cannot be fully satisfied Then violations are minimized and reported as soft violations in the outcomes with counts and percentages
Preset Templates Apply Consistently
Given preset templates Follow the Sun, Region Rotation, and Weight by Role are available When the user applies a preset Then the corresponding parameter set (cadence, duration, windows, weights, strictness, anchor tz) is applied in a single history step, outcomes recompute within sub-second targets, and the preset name is indicated as active Given the user has modified parameters after applying a preset When the user re-applies the same preset Then parameters are reset to the preset’s defined values and differences from the current state are summarized in a change list Given presets are applied When the user hovers a preset Then a preview of parameter changes is shown without committing, and no recompute occurs until Apply is selected
Undo/Redo and Reset-to-Baseline Behavior
Given a sequence of parameter changes When the user invokes Undo (Ctrl/Cmd+Z or toolbar) Then the immediately preceding committed parameter set is restored and outcomes revert accordingly within sub-second targets Given Undo has been used When the user invokes Redo (Ctrl/Cmd+Shift+Z or toolbar) Then the reverted change is re-applied and outcomes update Given the user performs a continuous drag on a slider When the drag ends Then the entire drag is recorded as a single history entry Given Reset to Baseline is selected When confirmed Then all parameters return to the baseline defaults, a new history entry is created, and Undo can restore the pre-reset state
URL Persistence and Reproducible Simulation Sharing
Given any parameter change is committed (post-debounce) When the state updates Then the URL query string reflects the full parameter set within 200 ms without creating excessive history entries during continuous edits (use replaceState during drag, pushState on commit) Given a sharable URL is copied from the address bar When opened in a fresh browser session Then the simulator restores the exact parameter set, selected preset (if any), and produces identical fairness/compliance metrics and confidence deltas (within deterministic tolerance) to the source session Given the URL contains unknown or missing parameters When loaded Then the simulator ignores unknown keys, applies defaults for missing values, and still loads successfully
Compliance Constraints & Work‑Hours Guardrails
"As a people ops manager, I want simulations to respect work-hour policies and regional holidays so that recurring meetings remain compliant and sustainable for all participants."
Description

Model and enforce organization and regional policies during simulation, including local working-hour windows, country/state holidays, daylight savings transitions, maximum weekly after-hours occurrences, minimum recovery gaps between unfavorable slots, weekly hour caps, and required rest periods. Support both hard constraints (block scheduling) and soft constraints (penalize in scoring) with clear surfacing of violations and suggested remedies. Ingest policies from Timeglue org settings and regional calendars; allow per-person overrides with audit trails.

Acceptance Criteria
Local Working-Hour Windows Enforcement (Hard and Soft)
Given organization working_hours windows per participant and region are configured and active, and constraint mode = Hard When a quarter-long recurring series is simulated Then 100% of scheduled occurrences for each participant fall within that participant’s local working_hours window on their local calendar date And the number of hard violations for out-of-window placement equals 0 Given the same working_hours windows and constraint mode = Soft with penalty_weight configured When the same series is simulated Then occurrences outside the window are permitted, but each minute scheduled outside the window accrues penalties per configured penalty_weight and is recorded as a soft violation with person, date, and out-of-window minutes
Holidays Compliance
Given country/state holiday calendars are associated to each participant’s locale and synced for the simulation quarter, and constraint mode = Hard When the simulator generates occurrences Then no occurrence is placed on any participant’s local holiday date (all-attendee compatibility required), and the count of holiday hard violations equals 0 Given the same calendars and constraint mode = Soft with holiday_penalty configured When the simulator generates occurrences that intersect any participant’s local holiday Then those occurrences remain scheduled, each accrues holiday_penalty per impacted participant, and a soft violation is logged listing the impacted participants and local dates
Daylight Saving Time Transition Accuracy
Given at least one participant’s region observes a DST transition within the simulation quarter When a weekly series spanning the DST boundary is simulated Then per-date timezone offsets are applied (no fixed-offset assumption), resulting in the planned number of occurrences with no skipped or duplicated instances And for each participant, the local start time of each occurrence is evaluated against that participant’s working_hours after applying the correct offset for that date And fairness scoring uses the post-offset local clock time for all penalty/constraint evaluations And the simulator timeline renders allowed windows on the DST week using the correct local wall-clock shift
Weekly Limits: After-Hours Cap, Hour Caps, Required Rest
Given organization policy sets max_after_hours_per_week = 2, weekly_meeting_hours_cap = 10h, required_daily_rest_hours = 11h, required_weekly_rest_hours = 24h, and constraint mode = Hard When the simulator generates a quarter-long plan Then for each participant and each ISO week: count(after-hours occurrences) ≤ 2, total scheduled meeting duration ≤ 10h, every gap between any two meetings ≥ 11h, and every rolling 7-day window contains ≥ 24h continuous rest And the number of hard violations for these limits equals 0 Given the same limits and constraint mode = Soft with per-metric penalty weights configured When the simulator generates the plan Then exceedances are permitted but each excess occurrence/hour/rest shortfall incurs the configured penalty and is logged as a soft violation with person, week, metric, expected vs actual, and penalty applied
Minimum Recovery Gap Between Unfavorable Slots
Given policy defines unfavorable slots (e.g., after-hours or red-band windows) and min_recovery_gap_hours = 36, with constraint mode = Hard When a participant receives an unfavorable occurrence Then the next scheduled occurrence for that participant starts at least 36 hours after the end of the unfavorable occurrence And the number of hard violations for recovery gap equals 0 Given the same policy with constraint mode = Soft and penalty_weight configured per hour of shortfall When a participant’s next occurrence starts before 36 hours have elapsed Then a soft violation is recorded with the actual gap, shortfall hours, and penalty = shortfall × penalty_weight
Policy Ingestion and Per-Person Overrides with Audit Trails
Given organization compliance policies are defined in Timeglue org settings and regional calendars and have version ids and effective dates When the simulator initializes a run Then it loads the latest effective policy versions for the run dates, records the policy_version_ids in the simulation metadata, and uses those values for all evaluations Given a per-person override for working hours and limits with an effective period is saved When the simulator runs for dates within the override period Then override values supersede org defaults for that person only, and all evaluations use the override And an audit record is created capturing actor_id, timestamp, scope (person), fields changed (before → after), effective period, and reason, and the record is immutable and exportable And rerunning the same configuration reproduces the same policy_version_ids and override references in the report
Violation Surfacing and Suggested Remedies
Given any constraint violation (hard or soft) occurs during simulation When results are presented in the Fairness Simulator Then each violation is listed per person and occurrence with rule_id, rule_name, severity (Info|Warning|Error), hard_or_soft, local date/time, time range, expected vs actual, and fairness score impact (Δ) And the UI provides at least two actionable suggestions per violation (e.g., shift by N minutes into window, swap rotation week, adjust cadence) with predicted Δ fairness and Δ violations And users can filter violations by person, rule, severity and export a CSV containing all listed fields And applying a single suggestion updates the simulation, re-evaluates constraints and scores, and refreshes the results within ≤ 2 seconds
Equity Visualizations & Explainability
"As a team lead, I want clear visuals and explanations of who is taking the hit and why so that I can justify the plan and negotiate adjustments transparently."
Description

Deliver interactive visualizations that make equity legible: per-person heatmaps across local time bands, bar charts of early/late counts, region-level fairness summaries, and trend lines across the quarter. Highlight outliers and violations with tooltips that explain contributing constraints and weight effects. Provide an explainer panel that shows how the overall fairness score is calculated, including component weights and penalties, with accessible color palettes, keyboard navigation, and screen-reader-friendly descriptions. Support exporting charts as PNG/SVG for stakeholder reviews.

Acceptance Criteria
Per-Person Local Time Heatmap Rendering & Navigation
- Given a simulation with up to 200 participants across ≥4 regions and 13 weeks, When the Equity Visualizations view loads, Then a per-person heatmap renders within 1500 ms showing local time bands (00:00–23:59) with a color scale mapped to burden score. - Given the heatmap is rendered, When the user hovers or keyboard-focuses a cell, Then a tooltip appears within 150 ms showing participant name, local time range, early/late/after-hours counts for the quarter, and the burden score for that band. - Given a heatmap cell has focus, When Arrow keys are pressed, Then focus moves to the adjacent cell and the tooltip updates; When Enter is pressed, Then the cell toggles selection for comparison mode. - Given WCAG AA requirements, When the heatmap is displayed, Then all color pairs meet contrast ratio ≥ 4.5:1 and high-burden cells include a non-color indicator (pattern/outline). - Given the user changes cadence, duration, or weights, When recalculation completes, Then the heatmap updates within 500 ms without a full-page reload. - Given screen reader usage, When a cell receives focus, Then aria-label announces "Participant, local time band, burden score, early X, late Y, after-hours Z."
Early/Late Counts Bar Chart with Outlier Highlighting
- Given policy thresholds (early < 08:00 local, late > 18:00 local by default), When the Early/Late chart is selected, Then each person shows a stacked bar with early and late segments and a total count label. - Given outlier detection configured as policy violation or z-score > 2.0, When bars are computed, Then outlier bars are visually highlighted and labeled accessibly as "Outlier". - Given a bar is hovered or focused, When the tooltip opens, Then it shows early count, late count, thresholds used, and % of total meetings. - Given keyboard navigation, When Tab/Shift+Tab are used, Then each bar is focusable in logical order and Enter opens a details drawer listing affected meetings. - Given filters by team or region, When a filter is applied, Then the chart recomputes within 400 ms and axes rescale to the filtered data. - Given missing data for a person, Then a "No data" state is shown instead of an empty bar.
Region-Level Fairness Summary Cards & Filtering
- Given regions are aggregated, When the Region Summary view loads, Then each region shows average fairness score, min, max, standard deviation, compliance rate (% meetings within work hours), and participant count. - Given sortable columns, When a column header is activated, Then rows sort ascending/descending within 200 ms and the sort state is announced to assistive tech. - Given region filter chips, When selecting/deselecting chips, Then only selected regions remain visible and summary totals recalculate accordingly. - Given any region has after-hours burden > 10% or fairness score below the configured threshold, Then the row displays a warning badge with an accessible description of the exceeded metric. - Given screen reader usage, When a row receives focus, Then it announces region name and all summary metrics in a single readable phrase.
Quarterly Trend Lines & Instant Updates
- Given weekly fairness scores per person and per region for the quarter, When the Trend view opens, Then line charts render within 1000 ms with weekly points and axis labels. - Given the user modifies cadence, duration, or weights, When changes are applied, Then trend lines recompute and redraw within 500 ms and display a delta indicator versus the prior state. - Given legend items, When a legend item is toggled via click or Space, Then the corresponding series shows/hides and the y-axis rescales to visible data. - Given keyboard or pointer interaction, When Left/Right arrows or hover are used over the plot, Then focus snaps to the nearest point and tooltip/aria-live announces week and score.
Explainability Panel for Fairness Score Formula
- Given the explainer panel is opened, When it loads, Then it displays the fairness score formula with component weights that sum to 100% and lists penalties with current values. - Given weights are adjusted in the simulator, When the panel is open, Then component values update within 300 ms and the displayed total score matches chart values within ±0.1. - Given an info icon next to each component, When activated, Then a popover explains the component’s definition, data source, and links to relevant constraints. - Given keyboard navigation, When tabbing through the panel, Then focus order follows visual order, all controls have visible focus, and screen readers announce names and current weights. - Given a need to share the formula, When "Copy formula" is clicked, Then a plaintext representation of the formula is copied to the clipboard successfully.
Violation & Constraint Tooltips on Data Points
- Given a data point is marked as a violation or outlier, When the user hovers or focuses it, Then a tooltip lists triggered constraints (e.g., holiday, focus block), the weight contribution (%) of each, and the policy rule ID/name. - Given multiple contributing constraints, When the tooltip is shown, Then contributions are sorted by impact descending and sum to 100% (±1% rounding). - Given the details link in the tooltip is activated, When clicked or Enter is pressed, Then a side panel opens listing specific meeting instances and local times causing the issue. - Given accessibility requirements, When the tooltip opens, Then it is announced to screen readers with role=dialog and can be dismissed with Escape.
Chart Export to PNG/SVG with Accessibility Metadata
- Given a chart or heatmap is visible, When Export > PNG is selected, Then a PNG downloads at 2× pixel density including title, legend, axes, and annotations, with file size ≤ 2 MB for up to 200 data points. - Given Export > SVG is selected, When the file downloads, Then it preserves vector fidelity, embeds or substitutes fonts safely, and includes <title> and <desc> elements conveying chart purpose and summary. - Given dark or light theme is active, When exporting, Then the output matches the current theme and maintains text/mark contrast ≥ 4.5:1. - Given the user has filtered or hidden series, When exporting, Then only the visible state is captured in the output. - Given an export error occurs (e.g., cross-origin image), When export fails, Then a non-blocking error message explains the cause and suggests remediation (e.g., "Enable CORS" or "Remove background image").
Scenario Save, Compare, and Share
"As an engagement lead, I want to save and compare multiple simulation scenarios and share a report so that stakeholders can review options and approve a fair plan."
Description

Enable saving named scenarios that capture input parameters, seeds, and results, with baseline tagging and timestamps. Provide side-by-side comparison of scenarios, showing deltas in fairness scores, violations, and distribution metrics at person and region levels. Generate shareable, permissioned links for view-only access and export PDF/CSV reports summarizing assumptions, constraints, and projected outcomes for sign-off.

Acceptance Criteria
Save Named Scenario with Baseline Tag and Timestamp
Given I have completed a simulation with defined parameters, seeds, and results When I click "Save Scenario", enter a unique name within my workspace, optionally toggle "Set as Baseline", and confirm Then the scenario is persisted with all input parameters, seed value(s), and computed results And a created_at timestamp in ISO 8601 UTC is stored and visible in the scenarios list And if the name collides within the workspace, I am prompted to choose a different name before saving And if "Set as Baseline" is enabled, the previous baseline (if any) is unset and the new scenario is flagged as baseline And the scenario appears in the scenarios list with correct badges within 2 seconds of confirmation
Load/Update Scenario and Deterministic Re-run Using Saved Seed
Given a saved scenario exists with parameters and a seed When I open the scenario, make no changes, and re-run the simulation Then the fairness scores and violation counts reproduce the saved results within a tolerance of ±0.001 for scores and exact match for counts And when I modify cadence, duration, or weights and re-run Then I can choose "Save as New" to create a new scenario or "Update Existing" to overwrite the original And choosing overwrite requires explicit confirmation and updates updated_at (ISO 8601 UTC) without changing created_at And the seed value persists unless I explicitly change it
Side-by-Side Comparison: Person-Level Fairness Deltas
Given two saved scenarios A and B are selected for comparison When I open the Compare view and select the Person tab Then each person appears once with their fairness score for A and B side-by-side And a delta column (B−A) is shown with sign and two-decimal precision And deltas are color-coded (improve ≥ +0.01 = green, worsen ≤ −0.01 = red, |delta| < 0.01 = gray) And I can sort by name, score A, score B, or delta And persons present in only one scenario are marked N/A for the other and excluded from aggregate delta statistics And an aggregate dispersion metric (e.g., standard deviation) is shown for A and B with a delta
Side-by-Side Comparison: Region-Level Violations and Distribution Metrics
Given two saved scenarios A and B are selected for comparison When I open the Compare view and select the Region tab Then for each region I see counts for work-hours violations, holiday conflicts, and focus-block collisions for A and B And delta counts (B−A) are displayed with sign And distribution metrics per region are shown (mean local meeting time, off-hours percentage) for A and B And I can filter regions and the totals row updates accordingly And totals equal the sum of visible regions
Default Compare Against Baseline
Given a baseline scenario exists in the workspace When I open the Compare view without specifying a reference Then the baseline is preselected as Scenario A and my currently active scenario is preselected as Scenario B And a "Baseline" badge is displayed next to Scenario A And if no baseline exists, I am prompted to choose a reference scenario before continuing
Shareable View-Only Link with Permissions and Expiry
Given I am viewing a saved scenario or a comparison When I click Share Link and choose View-Only access Then a unique URL with at least 128 bits of entropy is generated and scoped to view-only permissions And I can set an expiration (7, 30, or 90 days) and optionally revoke the link at any time And attempting to use an expired link returns an expiration message and blocks access And attempting to use a revoked link returns an access revoked message and blocks access And viewers using the link cannot modify scenarios, re-run simulations, or change permissions
Export PDF/CSV Reports with Assumptions and Outcomes
Given I am viewing a saved scenario or a scenario comparison When I click Export PDF or Export CSV Then the exported file includes scenario name, baseline indicator, seed value, created_at (UTC), and exporting user And assumptions and constraints are included: cadence, duration, weights, work hours windows, holidays observed, and focus blocks And person-level and region-level outcomes are included: fairness scores, violations, and distribution metrics with two-decimal precision And CSV uses defined column headers and UTF-8 encoding; PDF includes an executive summary, tables, and key charts And the export completes within 10 seconds for up to 500 participants and 20 regions And the filename follows pattern: {scenario-or-compare}-{YYYYMMDDTHHMMSSZ}.{pdf|csv}
Performance & Scale Targets
"As a product user, I want the simulator to feel instantaneous at realistic team sizes so that experimentation doesn’t interrupt my planning flow."
Description

Meet non-functional targets required for instant feedback: recompute results in under 1.5 seconds for up to 100 participants across 10 regions over 13 weeks with at least three candidate windows per week. Implement incremental diff recomputation, memoization, and worker-thread offloading to avoid UI jank, with graceful fallback to background jobs if thresholds are exceeded. Provide telemetry for compute time, cache hit rates, and constraint-violation density, and set guardrails for memory usage and concurrency to support simultaneous simulations.

Acceptance Criteria
P95 recomputation under target load within 1.5s
Given a simulation with 100 participants across 10 regions over 13 weeks and ≥3 candidate windows per week When the user modifies cadence, duration, or weights Then projected fairness scores and compliance indicators render within 1.5 seconds at P95 and within 2.0 seconds at P99, measured from input change to paint complete And partial UI remains responsive throughout the recompute cycle
Incremental diff recomputation correctness and scope
Given an existing computed simulation baseline When a single parameter change impacts ≤20% of weeks or candidate windows Then only affected weeks/windows are recomputed and unaffected outputs are returned from cache And resulting fairness and compliance outputs are numerically equivalent to a full recomputation within 1e-9 relative tolerance And CPU time is reduced by ≥50% versus a full recomputation for the same change
Memoization cache hit effectiveness for repeated parameter toggles
Given a user alternates one parameter between two values across 10 consecutive runs on the same dataset When recomputations are executed Then the aggregate cache hit rate across those runs is ≥70% And the first warm cache hit occurs within the first 3 runs And cache invalidates correctly (hit rate ≤5%) when any input hash differs
Main-thread responsiveness during heavy compute
Given compute is offloaded to worker threads When the user drags the timeline or edits weights during recomputation Then main-thread long tasks (>50 ms) do not exceed 1 in any rolling 5-second window And average FPS is ≥55 and first input delay is ≤50 ms during recompute cycles And total blocking time is ≤100 ms per 5-second window
Graceful fallback to background job on threshold breach
Given the runtime predictor estimates a recompute will exceed 1.5 seconds or memory guardrails When the user triggers the recompute Then the job is queued to a background worker within 200 ms and the UI remains interactive And a progress indicator with ETA appears within 300 ms and results auto-apply on completion without requiring a page refresh And the user can cancel the job; no partial results are applied on cancel; and an error is surfaced if the job exceeds a 30-second timeout
Telemetry completeness and accuracy for performance metrics
Given any recompute run (foreground or background) When the run starts and ends Then telemetry emits events for compute_time_ms, cache_hit_rate, constraint_violation_density, memory_used_mb, and concurrency_queue_depth with trace and correlation IDs And 99% of telemetry events are available in the backend within 60 seconds and contain no PII And dashboards expose P50/P95/P99 for compute_time_ms and cache_hit_rate with at least 24-hour retention
Memory and concurrency guardrails under simultaneous simulations
Given 20 concurrent simulations at target load characteristics When they are executed on a single node Then per-simulation peak memory is ≤150 MB and process RSS remains ≤1.5 GB And no more than 5% of runs exceed the 1.5-second recompute target; excess requests are queued with visible backpressure And GC pause times remain ≤50 ms at P95

Prime Hour Shield

Reserve premium booking windows for approved roles and account tiers while directing others to off‑peak options. Slots reveal dynamically based on SSO groups and account tier, protecting team focus time and prioritizing top customers without manual policing.

Requirements

SSO Group & Account Tier Mapping
"As a workspace admin, I want Prime Hour Shield to recognize users’ SSO groups and account tier so that prime booking access is granted automatically without manual list management."
Description

Integrate with enterprise identity providers via SAML/OIDC to ingest user identity and group membership, and map billing/CRM account tiers into an internal access level used by Prime Hour Shield. Support SCIM for automated group sync, domain-based heuristics for guests, and a policy mapping table (group/tier → prime access rule). Provide short-lived caching with graceful degradation when identity is unknown and admin tools to test mappings and resolve conflicts. Enforce data handling standards (SOC 2, GDPR) and least-privilege access to identity data.

Acceptance Criteria
SAML/OIDC Identity and Group Ingestion
Given a tenant has configured a trusted SAML/OIDC IdP with issuer/metadata and signing keys When a user authenticates and a valid assertion/id_token with subject, email, and group claims is received Then the token signature, audience, and expiry are validated; invalid or replayed tokens are rejected with HTTP 401 And the user is JIT-provisioned/updated with user_id, email, domain, and group IDs stored And group membership is normalized from claims (e.g., groups, memberOf, or configured custom claim) And the user's account tier is fetched from billing/CRM and included in the mapping input And the mapping engine computes an internal access_level for Prime Hour Shield within 200 ms P95
SCIM Group Sync and Drift Correction
Given SCIM 2.0 is enabled with valid credentials and endpoints When the IdP triggers a group/user change webhook or during scheduled polling every 15 minutes Then incremental sync updates local group memberships within 5 minutes P95 And deprovisioned users lose any access_level above guest within 15 minutes And a daily full sync reconciles drift and removes stale memberships And sync failures retry with exponential backoff up to 1 hour and alert tenant admins within 5 minutes on sustained failure And referential integrity ensures no duplicate or orphaned group records post-sync
Domain-Based Guest Heuristics
Given a tenant has configured one or more verified company email domains When a user's email domain is not verified and the user is not in any mapped SSO group Then the user is classified as guest and assigned the least-privileged access_level And when the domain becomes verified or the user is added to a mapped group, classification updates within 10 minutes And admins can whitelist specific external domains or addresses to override guest classification with an expiration
Policy Mapping Table and Precedence
Given a policy table maps {SSO group (ID/regex), account_tier, role} to {access_level, prime_access_rule} When multiple rules match a user Then precedence is deterministic: explicit user override > SSO group match > account tier match > guest default And exactly one effective rule is selected and its rule_id and source are recorded in audit logs And policy changes take effect immediately for new decisions and within 5 minutes for cached entries And computed access_level and prime_access_rule are exposed to Prime Hour Shield APIs to control slot visibility and booking eligibility
Caching and Graceful Degradation
Given identity decisions are cached per user_id with a TTL of 5 minutes When IdP, billing, or CRM lookups fail or time out Then a fresh cached decision is returned; otherwise the least-privileged access_level is applied and prime-hour slots are hidden And booking APIs do not return 5xx due to identity failures (respond with 403 or 200 with filtered data as appropriate) And metrics for cache hit rate, fallback count, and decision latency are emitted; alerts trigger if fallback >1% over any 15-minute window And cache entries are invalidated on SCIM updates and policy changes
Admin Mapping Test & Conflict Resolution Tool
Given an admin with Identity Admin role accesses the Mapping Tester When the admin inputs a user email or uploads a sample SAML/OIDC token Then the tool displays parsed attributes, groups, account tier, evaluated rules in order, effective access_level, and prime_access_rule And the admin can simulate changes (e.g., add group, change tier) without persisting and see the recomputed result instantly And the admin can resolve conflicts by reordering rules or creating a time-bound user override; all actions are audited with who/what/when and before/after And RBAC restricts access to authorized admins and enforces MFA when enabled
Data Handling & Least-Privilege Compliance
Given the service processes personal identity data When storing and accessing identity attributes Then only user_id, email, email_domain, group IDs, account_tier, last_sync_at, effective_access_level, and audit metadata are persisted And all data is encrypted in transit (TLS 1.2+) and at rest (AES-256 or managed KMS) And least-privilege access is enforced for service accounts and admins via RBAC; all access is logged and reviewed And a user DSAR export is producible within 30 days and deletion requests are fulfilled within 30 days And logs retain personal data no longer than 90 days and contain no secrets or tokens
Prime Window Policy Builder
"As a team lead, I want to define and preview prime booking windows for my team so that we protect focus time while prioritizing key customers."
Description

An admin UI and backend model to define prime booking windows per team, host, and calendar. Policies support days of week, time ranges in the host’s time zone, optional daily/weekly quotas, blackout dates, and per-role/tier eligibility. Include validation to prevent overlapping or contradictory rules, inheritance from workspace defaults, versioning with effective dates, and change logs. Policies must layer on top of existing work hours, holidays, and focus blocks and never expand availability beyond baseline constraints. Provide preview and “simulate as role/tier” capabilities to verify outcomes before publishing.

Acceptance Criteria
Create Prime Window with Days, Time Ranges, Quotas, Blackouts, and Eligibility
Given an admin opens the Policy Builder for a specific team/host/calendar When they define prime windows with days of week (e.g., Mon–Thu), two time ranges (09:00–11:00, 15:00–17:00) in the host’s time zone, daily quota = 3, weekly quota = 8, a blackout date (e.g., 2025-09-15), and eligibility (SSO roles: AE, CSM; account tiers: Enterprise, Pro) And they save the policy as Draft Then the policy is persisted as Draft with all fields exactly as entered And all time ranges are stored and displayed in the host’s time zone And the policy scope (team vs host vs calendar) is correctly associated and retrievable via API and UI
Validation Prevents Overlaps and Invalid Inputs
Given a policy with multiple time ranges and quotas When an admin attempts to save overlapping time ranges within the same day for the same scope Then the save is blocked with a descriptive error indicating the overlapping ranges Given a time range where end time ≤ start time When the admin attempts to save Then the save is blocked with a field-level error Given quotas provided as negative or non-integer values When the admin attempts to save Then the save is blocked and the field indicates valid ranges (0–999) Given duplicate blackout dates or dates outside the policy’s effective period When the admin attempts to save Then the save is blocked with a message identifying the exact conflicts Given an existing Published policy version effective for the same scope and period When the admin attempts to publish another version that introduces overlapping prime windows for the same effective interval Then publishing is blocked and the error references the conflicting policy/version
Inheritance from Workspace Defaults with Predictable Precedence
Given workspace default prime window policies exist And a team-level policy is defined for Team A And a host-level policy is defined for Host H in Team A And a calendar-level policy is defined for Calendar C of Host H When the system resolves effective prime windows for Calendar C Then precedence is Calendar > Host > Team > Workspace Default And only the most specific applicable windows are used where conflicts occur And in the absence of a more specific policy, the next less specific policy applies Given the team-level policy is deleted When resolving effective windows for Host H Then the host-level policy remains in effect and any gaps fall back to workspace defaults
Versioning with Effective Dates and Change Logs
Given a Published policy version V1 with effective start 2025-09-01 00:00 host local time and no end date When an admin creates version V2 with effective start 2025-10-01 00:00 Then V1 remains active until 2025-09-30 23:59:59 host local time And V2 is scheduled and not active before its start Given an attempt to create a new version whose effective period overlaps an existing version for the same scope When saving or publishing Then the action is blocked with an overlap error Given a policy is updated and published When viewing the change log Then the log shows actor, timestamp, version, fields changed (including old→new), effective dates, and optional change reason And prior versions are read-only via UI and API
Baseline Constraints Never Expanded (Work Hours, Holidays, Focus Blocks)
Given host baseline availability is 09:00–18:00 and a focus block exists 17:30–18:00 on Tuesday And a prime window policy defines 17:00–19:00 on Tuesday When computing effective prime slots Then prime slots are trimmed to 17:00–17:30 only And no prime slots are produced outside baseline availability Given a company holiday on 2025-12-25 and a prime window on that date When computing effective prime slots Then zero prime slots are returned for that date And preview clearly indicates exclusions due to baseline constraints
Daily and Weekly Prime Slot Quotas Enforced
Given a policy with daily quota = 2 and weekly quota = 6 for Host H And six distinct prime-slot meetings are booked during a host’s work week When additional invitees attempt to book prime slots that would exceed the quotas Then the system hides or disables prime slots once the applicable quota is exhausted And off-peak options are still offered Given the host’s time zone is UTC+2 When the day changes at 00:00 UTC+2 Then the daily prime quota counter resets Given a workspace week-start setting (e.g., Monday) When the week boundary is reached at 00:00 host local time on the configured day Then the weekly quota counter resets Given a prime-slot booking is canceled before its start time When recalculating quotas Then the consumed count is decremented and prime slots may reappear if under quota
Preview and Simulate as Role/Tier Before Publishing
Given a Draft policy defines prime windows eligible for roles AE, CSM and tiers Enterprise, Pro When an admin uses Preview for Host H over a selected date range Then the preview displays prime windows in the host’s time zone with a legend for baseline exclusions, blackouts, and quotas Given Simulate with role=AE and tier=Pro When running the simulation Then prime slots are shown per effective policy and baseline constraints Given Simulate with role=Prospect and tier=Free When running the simulation Then prime slots are not shown and only off-peak options appear Given the same inputs are evaluated by the scheduling API When comparing results between Preview/Simulate and the API Then the sets of prime and non-prime slots are identical
Identity-Aware Dynamic Slot Rendering
"As a prospective booker, I want the booking page to automatically show me the appropriate time slots for my status so that I can schedule quickly without confusion or denied bookings."
Description

The booking experience must dynamically reveal or withhold prime-time slots based on the viewer’s authenticated identity, SSO group membership, and account tier. Support a seamless flow where off-peak is shown by default and prime slots are revealed after sign-in or token verification without a full reload. Hidden slots must be non-enumerable and protected by server-side authorization, signed link tokens, and client-side guards. Implement edge caching keyed by authorization state to maintain low latency under load.

Acceptance Criteria
Default Off‑Peak Rendering for Anonymous and Ineligible Viewers
- Given an unauthenticated viewer opens a booking page for an event with defined prime windows, When the page first renders, Then only off-peak slots are displayed and no prime slot data is present in the initial slots API response. - Given an authenticated viewer lacking the required SSO group or account tier, When requesting slots, Then the API returns only off-peak slots and the UI does not render placeholders for prime slots. - Given network retries or pagination over the visible date range, When additional slot pages are requested, Then no prime slot identifiers or metadata are returned for ineligible viewers.
Prime Slots Reveal Inline After Successful SSO for Eligible Viewers
- Given a booking page showing off-peak slots, When the viewer completes SSO and their group and account tier meet prime access policy, Then prime slots are appended into the existing timeline without a full page reload. - Given the identity change event, When prime slots are revealed, Then the update completes within 500 ms P95 from SSO callback receipt and the current scroll/filters are preserved. - Given the viewer remains eligible, When navigating within the date range, Then subsequent slot fetches include prime slots.
Non-Enumeration and Server-Side Authorization of Prime Slots
- Given an ineligible viewer calls the slots endpoint for any range intersecting prime windows, When the response is returned, Then prime slots are omitted and response schema, status codes, and cache headers are indistinguishable from eligible responses aside from the slot list. - Given parameter probing (changing ranges, sizes, cursors) by an ineligible viewer, When comparing responses, Then total counts and pagination cursors do not leak the count or positions of prime slots. - Given a direct booking attempt using a guessed prime slot ID by an ineligible viewer, When the server processes the request, Then it returns 403 Forbidden without revealing whether the slot exists and suggests off-peak alternatives in the payload.
Signed Smart-Link Token Unlocks Prime Slots
- Given a booking URL containing a valid, unexpired, server-signed token scoped to a specific event and prime access, When an unauthenticated viewer opens the link, Then prime slots are displayed and are bookable subject to server-side token verification. - Given an expired, tampered, or scope-mismatched token, When the link is opened, Then prime slots are not displayed and protected API calls return 401/403 with no prime slot metadata in the payload. - Given a valid token, When the token reaches expiry during the session, Then prime slots are removed on the next slots refresh and booking attempts fail with 401/403.
Edge Caching Keyed by Authorization State
- Given two viewers (eligible and ineligible) requesting the same event and time range through the edge, When responses are served, Then each receives slots consistent with their authorization without cache leakage between auth states. - Given warm cache conditions, When serving slot queries, Then P95 time-to-first-byte is ≤ 150 ms and cache hit ratio is ≥ 85% for repeated identical requests within the TTL. - Given cache keys/headers, When inspecting edge behavior, Then the cache is segmented by authorization bucket and account tier, and purges/invalidation propagate within 60 seconds of access policy changes.
Real-Time Access Change Handling Within Session
- Given a viewer’s eligibility changes (SSO group or account tier updated) while the booking page is open, When the client refreshes auth state or fetches slots next, Then the visible slot set updates to match the new policy without a full page reload. - Given eligibility is revoked, When the UI updates, Then previously shown prime slots are removed and any pending booking attempts on those slots are rejected server-side with 403 and a message directing to off-peak options. - Given eligibility is granted, When the UI updates, Then prime slots appear within 60 seconds of the change being recognized by the system.
Off-Peak Routing & Custom Messaging
"As a standard-tier customer, I want clear guidance to suitable off-peak times and upgrade options so that I can still book efficiently or understand how to gain priority."
Description

When a viewer lacks prime access, automatically route them to off-peak options with contextual messaging and CTAs tailored to their tier (e.g., “Request prime time,” “Upgrade for priority access”). Support customizable copy, localization, and per-link configuration. Respect the viewer’s time zone, surface the earliest acceptable off-peak times, and avoid after-hours in the booker’s region. Emit events for impressions, clicks, and conversions to support ongoing optimization.

Acceptance Criteria
Non-Prime Viewer Routed to Off-Peak with Tier-Specific CTA
- Given a public booking link with prime windows configured and a viewer identified without prime access, When the viewer loads the link, Then prime windows are hidden and only off-peak windows render within 500 ms of calendar load. - Given the viewer's tier, When the page renders, Then the primary CTA reflects the tier-specific action text configured for that tier (e.g., "Request prime time" for Free, "Upgrade for priority access" for Basic). - Given a viewer without prime access, When they view the schedule, Then the top three earliest off-peak slots within the next 14 days are displayed above the fold. - Given the viewer clicks the tier-specific CTA, When the action is available, Then the appropriate flow is initiated (request form modal or upgrade URL) and the click is tracked.
Time Zone Respect and After-Hours Guardrail
- Given the viewer’s IANA time zone is detected or manually selected, When off-peak slots are displayed, Then all times are shown in the viewer’s local time zone and include a clear TZ label. - Given the booker’s working hours and focus blocks, When computing off-peak recommendations, Then no proposed slot starts outside the booker’s working hours or within focus blocks. - Given a conflict where the earliest off-peak slot would violate the booker’s after-hours policy, When generating the list, Then the algorithm selects the next valid slot chronologically that satisfies both viewer-local display and booker-hours constraints. - Given no valid off-peak slots exist in the next 30 days, When rendering, Then display a "No off-peak availability" message and surface the tier-specific CTA.
Per-Link Copy and CTA Override
- Given workspace default copy and CTAs and per-link overrides, When a viewer opens that specific link, Then the per-link copy and CTA texts are used; if an override is unset, the workspace default is used. - Given an editor updates per-link off-peak message text, When the link is refreshed, Then the new text appears within 2 minutes and is served to 100% of subsequent viewers. - Given a link has per-tier CTA mapping, When a viewer with that tier loads the page, Then the mapped CTA is visible and other CTAs for different tiers are not rendered in the DOM.
Localization of Messages and Formats
- Given supported locales configured (at minimum: en, es, fr, de, ja, pt-BR), When the viewer’s Accept-Language header or profile locale matches a supported locale, Then all off-peak messages and CTAs render in that locale; otherwise they fall back to en. - Given a right-to-left locale (e.g., ar), When enabled, Then messages and CTAs render RTL with correct punctuation and date formatting. - Given locale-specific date/time formats, When off-peak times are shown, Then formats follow locale conventions (e.g., 24h vs 12h, day-month order).
Analytics Events for Impressions, Clicks, and Conversions
- Given a viewer loads an off-peak–routed page, When the off-peak module is visible, Then emit an off_peak_impression event exactly once per session with payload: {viewer_tier, viewer_timezone, link_id, locale, variant_id}. - Given the viewer clicks the tier-specific CTA, When the click occurs, Then emit a cta_click event with payload {cta_type, link_id, viewer_tier, locale} before navigation, with a maximum 200 ms delay. - Given the viewer completes an off-peak booking, When the confirmation page renders, Then emit an off_peak_conversion event with payload {meeting_type_id, slot_start_utc, link_id, viewer_tier} and mark the session as converted to prevent duplicate conversions. - Given networking failures, When events cannot be sent, Then queue and retry with exponential backoff up to 3 times and drop after 24 hours.
Access Determination via SSO Groups and Account Tier
- Given SSO is enabled and group claims are present, When the viewer loads the booking link, Then their prime access is determined by the configured SSO group-to-access mapping; if ambiguous, deny prime and route to off-peak. - Given no authenticated identity or missing claims, When access cannot be determined within 300 ms, Then treat the viewer as non-prime and route to off-peak with a generic CTA. - Given a change in the account tier mid-session, When the page is reloaded, Then access rules re-evaluate and the correct slots and messaging render accordingly.
Earliest Acceptable Off-Peak Slot Selection
- Given meeting duration, buffers, holidays, and focus blocks, When computing earliest off-peak options, Then select the next 5 earliest valid off-peak start times within the next 21 days that satisfy host working hours, slot availability, and off-peak window definitions. - Given ties across calendars, When multiple calendars have the same earliest time, Then sort by soonest date, then start time, then host priority. - Given per-link off-peak window definitions, When computing, Then honor per-link windows; if none configured, use workspace default off-peak definition.
Admin Overrides & Request Access Workflow
"As a host, I want to grant a one-time exception for a customer to book during prime hours so that I can handle urgent situations without changing our policies."
Description

Provide a lightweight approval workflow to grant temporary prime access for specific invitees or events. Include one-time access tokens, expirations, and Slack/email notifications to approvers. Approved access should immediately unlock prime slots on the recipient’s booking page and be fully auditable. Support bulk exceptions, revocation, and safeguards against token sharing by binding tokens to email identity and short TTLs.

Acceptance Criteria
One-Time Prime Access Token Bound to Recipient Email
Given an approver approves prime access for invitee@example.com When the system issues a one-time token with a configurable TTL between 15 minutes and 24 hours (default 2 hours) Then only a user authenticated as invitee@example.com can use the token to view and book prime slots And when the token is redeemed for a prime slot booking, the token becomes immediately invalid for any further use And when a different email identity attempts to redeem the token, redemption is blocked and logged with reason "email-mismatch" And when the token TTL expires without redemption, the token cannot unlock prime slots and the invitee sees an expiration message
Immediate Unlock of Prime Slots After Approval
Given an approver approves temporary prime access for invitee@example.com on booking link L (or event E) When invitee@example.com opens or refreshes the booking page for L (or E) Then prime slots become visible and bookable within 5 seconds and remain so until single-use redemption or expiration, whichever occurs first And if the approver denies or rescinds before a booking occurs, prime slots do not display And prime slot visibility is scoped only to the approved link(s)/event(s); other links remain unchanged
Slack and Email Approval Notifications with Inline Actions
Given a request for temporary prime access is submitted for booking link L When notifications are triggered Then the designated approver(s) receive a Slack DM and an email within 60 seconds containing requester identity, requested window, link, and Approve/Deny actions And when an approver clicks Approve, the decision is recorded, the requester is notified of approval, and a token is issued And when an approver clicks Deny, the decision is recorded, the requester is notified of denial, and no token is issued And if multiple approvers are configured, the first recorded decision finalizes the request; subsequent actions are ignored and logged
Full Audit Trail of Override Lifecycle
Given any request, approval/denial, token issuance, redemption, expiration, or revocation occurs When an admin views the audit log Then each event displays actor, action type, subject email, link/event ID, timestamp (UTC), and outcome; IP and user agent are captured where applicable And entries are immutable, paginated, filterable by date range, actor, subject email, and link/event ID, and exportable to CSV And upon token redemption, the associated booking ID is recorded and linked in the audit trail
Bulk Exceptions via CSV Upload or Paste
Given an admin uploads a CSV or pastes a list of up to 500 emails with optional expiration and notes for link L When validation runs Then invalid emails are flagged with line numbers and reasons; no tokens are issued for invalid rows And upon submission, tokens are issued for valid entries only, and the system returns a summary with counts of successes and failures plus a downloadable error report And partial failures do not block valid entries And processing of 500 valid entries completes within 2 minutes and respects rate limits
Immediate Revocation of Prime Access
Given an active token or grant exists for invitee@example.com When an admin revokes the token or grant Then within 5 seconds prime slots no longer display for invitee@example.com on subsequent page loads and the token cannot be redeemed And existing meetings booked using the grant remain scheduled; revocation prevents new prime bookings only And the revocation is logged with actor, reason, and timestamp, and the invitee receives an email notification of revocation
Safeguards Against Token Sharing
Given a token is issued to invitee@example.com with a short TTL When the booking page is accessed while authenticated as a different email or SSO identity Then the token does not unlock prime slots and the attempt is logged as "email-mismatch" And when simultaneous redemption attempts occur from multiple devices or IPs, only the first successful redemption is allowed; subsequent attempts are rejected and logged as "already-used" And token delivery links prevent leaking raw tokens via referrers, and tokens expire automatically after TTL or after single use, whichever occurs first
Analytics & Audit Trail
"As an operations manager, I want visibility into how prime hours are used and protected so that I can optimize policies and demonstrate SLA adherence."
Description

Capture metrics such as prime slot exposure rate, bookings by tier/role, blocked attempts, override usage, and time protected. Provide workspace dashboards, CSV export, and webhook/BI integrations. Maintain immutable audit logs for policy changes, access decisions, and token grants to meet compliance needs and facilitate incident investigation.

Acceptance Criteria
Workspace Dashboard: KPI Visibility and Filtering
Given I am a workspace admin with Prime Hour Shield analytics access When I open the Analytics dashboard and set a date range (UTC) and filters (SSO group, account tier, organizer, meeting type) Then I see KPIs: prime slot exposure rate, bookings by tier, bookings by role, blocked attempts, override usage count, time protected (hours), each with definition tooltips And KPI values update within 2 seconds after filter changes for datasets up to 1,000,000 events And KPI totals match the event store counts for the same filters and date range within 0.1%
CSV Export: Filtered KPI and Event Export
Given I am viewing the Analytics dashboard with filters/date range applied When I request a CSV export of events and aggregated metrics Then the export includes a header row, UTF-8 encoding, comma delimiter, and timestamps in ISO 8601 UTC And each event row includes: event_id, event_type, timestamp, workspace_id, organizer_id, viewer_id (nullable), role, account_tier, meeting_id (nullable), reason_code (nullable), prime_slot_flag (boolean), minutes_protected (integer, nullable), request_id, correlation_id And the aggregated metrics CSV includes daily buckets reflecting the selected filters And the download link is available within 60 seconds for up to 5,000,000 rows and expires after 24 hours And exported aggregates match dashboard values within 0.1% for the same filters and date range
Webhook Delivery: Analytics and Audit Events to BI
Given a workspace admin configures a webhook endpoint with a shared secret and enables event types (booking_created, blocked_attempt, prime_slot_exposed, override_used, policy_changed, access_decision, token_granted) When matching events occur Then the system POSTs JSON payloads within 5 seconds of event time with headers: X-Timeglue-Signature (HMAC-SHA256), X-Timeglue-Event-Id, X-Timeglue-Event-Type, X-Timeglue-Sent-At And deliveries are retried with exponential backoff for up to 24 hours on 5xx or network errors and are idempotent via an idempotency key And delivery outcomes are visible in the UI with last status, attempt count, and last error And payloads include schema_version and fields matching the CSV event schema, with PII limited to hashed identifiers
Immutable Audit Log: Policy Changes, Access Decisions, Token Grants
Given a Prime Hour Shield policy change, access decision, or token grant occurs When the action is committed Then an audit entry is appended with: audit_id, timestamp (UTC), actor_id, actor_role, action_type, target_id, previous_value (nullable), new_value (nullable), decision (allowed/blocked, nullable), reason_code (nullable), ip_address, user_agent, request_id, correlation_id, hash, prev_hash And the audit log is append-only and tamper-evident via a verifiable hash chain; a verification endpoint returns chain integrity status for a date range And entries are retained for at least 7 years, are read-only to workspace admins, searchable by actor/action/date, and exportable to CSV
Metric Computation: Prime Slot Exposure Rate
Given a reporting date range and filters When computing prime slot exposure rate Then exposure_rate = (count of unique invite views where at least one prime-designated slot was visible) / (count of unique invite views under the same filters) And unique invite views collapse repeat page loads by the same viewer within 30 seconds And bot traffic (per standard bot user-agent list) is excluded from numerator and denominator And the rate is displayed as a percentage with one decimal place and matches recomputation from the raw events within 0.1%
Metric Computation: Time Protected
Given a reporting date range and filters When computing time protected Then time_protected_minutes = sum of minutes that were hidden/blocked by Prime Hour Shield that would otherwise be available for booking And minutes outside organizer work hours or on organization-wide holidays are excluded And the value is exposed in hours on the dashboard (rounded to one decimal) and as minutes in CSV/webhook And the metric matches recomputation from raw events within 0.1%
Event Accuracy: Blocked Attempts and Override Usage
Given a user without requisite role/tier attempts to book a prime slot When the attempt is evaluated against Prime Hour Shield policy Then a blocked_attempt event is recorded with decision_id, reason_code, slot_time (ISO 8601 UTC), organizer_id, viewer_role, viewer_tier, and request_id And if an authorized approver uses an override, an override_used event is recorded with approver_id, scope (single_booking/window/policy), expiration (nullable), linked decision_id, and outcome And the dashboard counters for blocked attempts and override usage update in real time (under 5 seconds) and the events are visible in the audit log
Constraint-Aware Availability Engine Integration
"As a product owner, I want prime access to respect existing availability rules so that we never reveal time that violates work hours or focus blocks."
Description

Integrate Prime Hour Shield with the availability engine so that prime windows are computed as an overlay on baseline availability, which already honors work hours, holidays, and focus blocks. Implement deterministic conflict resolution to ensure prime windows never expose times blocked by the host’s calendar. Add robust tests across time zones, DST transitions, and multi-host meetings, plus guardrails and admin warnings if constraints eliminate all slots.

Acceptance Criteria
Prime Overlay Obeys Baseline Availability (Work Hours, Holidays, Focus Blocks)
Given a host’s baseline availability is computed from work hours, holidays, and focus blocks And a set of prime windows is defined for that host When the availability engine generates slots Then every exposed prime slot must fall within the baseline availability window And any portion of a prime window outside baseline availability is excluded from exposure And non-prime baseline slots remain available according to the event type’s configuration
Deterministic Conflict Resolution with Host Calendar Busy Events
Given a host has busy calendar events overlapping defined prime windows When generating available slots multiple times with identical inputs Then overlapping times are never exposed as available And the resulting slot set, order, and identifiers are identical across runs And the resolution priority is deterministic such that host busy events always supersede prime windows
Role and Tier-Based Prime Slot Revelation via SSO
Given an authenticated viewer belongs to an approved SSO group and the account tier allows prime access When opening a Prime Hour Shield booking link Then prime slots are visible in addition to off-peak slots Given a viewer lacks either the approved SSO group or the required tier When opening the same link Then prime slots are hidden and only off-peak slots are shown And attempts to retrieve prime slots via API or URL parameter manipulation are denied with 403 and no slot leakage
Multi-Host Meeting Intersection with Prime Overlay
Given a meeting requires availability from N hosts And each host has a computed baseline availability and defined prime windows When the system generates candidate slots Then the available slots are the intersection of all hosts’ baseline availability And prime slots are the intersection of all hosts’ prime windows further constrained by the baseline intersection And if no prime slots exist but off-peak slots do, show off-peak per viewer role/tier; otherwise show zero slots
Cross-Time-Zone and DST Transition Correctness
Given hosts and invitees are in different time zones and a DST transition occurs in any participant’s locale When generating slots spanning the transition window Then slot times reflect correct local UTC offsets before and after the transition And no duplicate or missing slots are produced due to the one-hour shift And prime slot boundaries are preserved relative to each host’s local time definition
Guardrails and Admin Warnings When Constraints Eliminate All Slots
Given constraints (work hours, holidays, focus blocks, busy events, and prime restrictions) eliminate all slots for an event type When an admin views the event type configuration or preview Then an explicit warning is displayed indicating zero availability and listing the blocking constraint categories And a guidance message suggests actions to restore availability (e.g., adjust prime windows or expand off-peak) And an audit log entry is recorded with timestamp and event type identifier
Off-Peak Fallback Preserves After-Hours Protection
Given a viewer without prime access opens a booking link And off-peak slots are available When slots are displayed Then all shown off-peak slots respect the host’s baseline constraints (no after-hours, holiday, focus block, or busy conflicts) And any attempt to book outside baseline constraints is rejected with a clear error message and no hold placed

Tier Sync

Auto-sync roles, account tiers, and entitlements from your IdP/HRIS/CRM (e.g., Okta, Azure AD, Salesforce) into Gatekeeper policies. Rules update in real time as people change teams or accounts upgrade, eliminating manual list maintenance and preventing misrouted bookings.

Requirements

IdP/CRM Connector Framework
"As a workspace admin, I want to connect our IdP/CRM to Timeglue so that roles, teams, and account tiers sync automatically without manual CSV uploads."
Description

Deliver prebuilt, configurable connectors for Okta, Azure AD/Microsoft Entra, Google Workspace, and Salesforce to ingest users, groups, org units, roles, account tiers, and entitlements into Timeglue. Support SCIM 2.0 and vendor REST APIs with OAuth 2.0/OIDC auth, least-privilege scopes, secure secret storage, pagination, delta queries, and rate-limit/backoff handling. Enable multi-tenant isolation, field selection, and initial full import with resumable checkpoints and retries. Normalize identities across sources and emit a canonical profile for Gatekeeper consumption.

Acceptance Criteria
Secure OAuth/OIDC Authentication & Secrets Management
Given a tenant admin initiates OAuth 2.0/OIDC setup for provider P (Okta/Azure AD/Google Workspace/Salesforce) When the consent screen is displayed Then only read/profile scopes necessary for user/group/org/tier/entitlement retrieval are requested, and no write/admin scopes are requested Given authorization is granted When tokens are received Then access and refresh tokens are encrypted at rest using the platform KMS/HSM, stored server-side, and never logged or exposed in UI/exports Given tokens approach expiry or are rotated by the provider When token refresh is needed Then the system refreshes tokens proactively without interrupting scheduled syncs and records an audit entry Given an auth failure (revoked consent, invalid_client, invalid_grant) When a sync attempts to use the connector Then the job is marked Degraded, retries use exponential backoff respecting Retry-After, and tenant admins receive an alert with remediation steps Given connector setup completes When audit logs are reviewed Then an entry exists with actor, provider, tenant, scopes granted, and timestamp, with no secret material present
SCIM Full and Delta Import (Okta/Entra/Google)
Given a configured SCIM 2.0 endpoint for provider P When an initial full import starts Then all users and groups are fetched via paginated requests until completion, with a durable checkpoint saved after each page Given a transient network/5xx error occurs during import When the job restarts Then it resumes from the last checkpoint without duplicating already-created/updated entities (idempotent upsert) Given the initial import completes When provider totals are compared to ingested totals Then the counts match exactly; otherwise the job is marked Failed with actionable error details Given a scheduled incremental sync When the provider supports lastModified filters Then only created/updated/deleted resources since the last successful sync are processed and reflected in Timeglue state Given HTTP 429 rate limiting is returned When issuing SCIM requests Then the client honors Retry-After, applies exponential backoff with jitter, and continues without data loss or job abortion
Salesforce Connector for Account Tiers and Entitlements
Given a Salesforce connected app with read-only scopes is authorized for a tenant When a sync runs Then Accounts, Users, PermissionSetAssignments, and configured custom objects are queried via SOQL with queryMore pagination until completion Given field selection and mapping rules are configured When mapping Salesforce fields to Timeglue tier and entitlement attributes Then tier and entitlement flags are computed deterministically per rules; unknown/missing values are quarantined and surfaced in a report to admins Given Salesforce API limits are being approached When remaining request quota drops below a configured threshold Then the client backs off to avoid breaching limits, emits metrics on throttling, and resumes when safe Given a change to an Account tier or User permission set occurs in Salesforce When the change is detected via event or polling Then Gatekeeper receives an idempotent entitlement update within 5 minutes
Multi-tenant Isolation and Data Boundary
Given tenants A and B exist When an authenticated user from tenant A requests connectors, jobs, logs, secrets, or data belonging to tenant B Then access is denied with 404/403 and no cross-tenant data is disclosed in responses or errors Given multi-tenant storage and queues When inspecting persisted records and job execution Then data is partitioned by tenant ID, encrypted per tenant, and jobs execute within tenant-scoped partitions Given a simulated compromise of a tenant access token When requests target other-tenant resources Then authorization prevents access consistently and security alerts are limited to the affected tenant Given observability dashboards and APIs are used When viewing metrics and logs Then tenant-level views expose only that tenant’s data; global views exclude tenant identifiers and secrets
Field Selection and Mapping Configuration
Given a tenant admin opens a connector configuration When selecting or deselecting fields per object type (user, group, org unit, account, entitlement) Then validation enforces required fields remain selected and changes are saved as a new mapping version Given configuration changes are saved When the next scheduled sync runs or a manual re-sync is triggered Then only the selected fields are requested from providers and included in the canonical profile output Given an invalid field or mapping rule is entered When attempting to save the configuration Then the save is blocked with a descriptive error and the prior active configuration remains in effect Given configuration updates occur When reviewing audit logs Then entries include before/after diffs, actor, timestamp, and tenant ID
Identity Normalization and Canonical Profile Emission
Given identities for the same person arrive from multiple sources When normalization executes Then records are deduplicated using configured keys (employeeId, externalId, primaryEmail) with deterministic precedence and source-of-truth tagging Given conflicting attribute values exist across sources When precedence rules are applied Then the selected value is recorded, losing values are preserved as provenance, and the decision is logged for auditability Given a canonical profile is created or updated When schema validation runs Then the profile conforms to the canonical schema (subjectId, fullName, primaryEmail, userType, roles, groups, orgUnit, managerId, accountTier, entitlements, sourceSystemIds) or is rejected with errors Given a canonical profile changes When publishing to Gatekeeper Then a change event is emitted within 60 seconds with at-least-once delivery and deduplication keys; failures route to a dead-letter queue with alerts Given a user is deprovisioned in a source system When processing deletions Then the canonical profile is marked inactive and Gatekeeper access is revoked within 5 minutes
Resumable Checkpoints, Retries, and Observability
Given any connector sync job runs When processing each page or batch Then a durable checkpoint is written; on restart, the job resumes from the last successful checkpoint without repeating side effects Given transient errors (network, 5xx, 429) occur When retries are attempted Then exponential backoff with jitter is used up to a configurable max; after max attempts, the job status is Failed and notifications are sent to tenant admins Given system health probes are called When liveness/readiness endpoints are hit Then Ready is reported only when dependencies are reachable and configuration is valid; otherwise Not Ready with reasons is returned Given metrics collection is enabled When scraping observability endpoints Then per-tenant/per-connector metrics include throughput, latency, error rate, retry count, rate-limit wait time, and last successful sync timestamp Given logs are generated during syncs When inspecting logs Then entries are structured with jobId and tenantId correlation, PII is minimized/masked, and secrets never appear
Real-time Event Sync & Webhooks
"As a security admin, I want changes to user roles or account status to reflect in Timeglue within minutes so that access and booking permissions remain accurate."
Description

Process near real-time updates by subscribing to provider event streams (e.g., Okta Event Hooks, Microsoft Graph change notifications, Salesforce CDC) and SCIM provisioning calls. Ensure idempotent, ordered application of events via a durable queue, with de-duplication keys and out-of-order handling, targeting sub-5-minute end-to-end propagation. Provide periodic reconciliation jobs as fallback, plus exponential retry, dead-letter queues, and backpressure to maintain freshness and resilience.

Acceptance Criteria
Verified Provider Webhooks & Subscription Handshakes
Given a provider sends a webhook/event hook with a valid signature/token (e.g., Okta, Microsoft Graph, Salesforce) When the event is received Then the endpoint validates authenticity and responds 2xx within 2 seconds And the payload is enqueued for async processing Given a provider sends a subscription verification challenge (e.g., Microsoft Graph validationToken, Okta one-time verification) When the challenge is received Then the service returns the expected token/body within the provider’s SLA (<=10 seconds) and the subscription is marked active Given a webhook is received with an invalid or missing signature/token When validation runs Then the request is rejected with 401/403, not enqueued, and an audit log entry is recorded with redacted metadata
Sub-5-Minute End-to-End Role Change Propagation
Given a user’s role or entitlement changes in an IdP/CRM supported provider When the change event is delivered to the system Then the corresponding Gatekeeper policies are updated and visible to API/UX within 5 minutes end-to-end (p95) and 10 minutes (p99) Given the change is applied Then an audit trail entry includes source provider, subject, change type, event id, processing latency, and result status Given monitoring is enabled When propagation exceeds the p95 target for 5 consecutive minutes Then an alert is emitted to on-call with current backlog depth and oldest message age
Idempotent Processing with Deduplication Keys
Given duplicate deliveries of the same provider event (identical event id or de-dup key) When messages are processed Then side effects are applied exactly once, with subsequent duplicates acknowledged without reapplying changes Given idempotency keys are stored with a TTL When a duplicate arrives within 24 hours Then processing is skipped and a dedup metric counter increments Given a message lacks a provider event id When enqueuing Then the system derives a deterministic de-dup key from payload fields per provider-specific strategy and documents the rule
Ordered Application with Out-of-Order Delivery Handling
Given two or more events for the same subject arrive out of order (e.g., role-removed after role-added) When processed Then the system applies them in correct logical order using provider sequence/version data or vector clocks, ensuring final state consistency Given provider events do not include sequence numbers When detected Then the system performs read-after-write reconciliation for the subject to ensure the applied state matches the latest provider truth Given the reordering window is exceeded (e.g., >15 minutes gap) When a late event arrives Then it is safely no-op’d or reconciled without regressing state, and an audit note is recorded
Exponential Retry, Backpressure, and Dead-Letter Queue
Given a transient downstream failure (5xx, network timeout) When processing an event Then the worker retries with exponential backoff starting at 5s, doubling up to a max backoff of 15 minutes, with jitter, for up to 10 attempts Given retries are exhausted or a non-retryable error occurs (4xx validation) When handling the event Then the message is moved to a DLQ with full context and a Pager alert is sent within 5 minutes Given webhook ingress load spikes When queue depth exceeds threshold (e.g., 10,000 messages or oldest age > 3 minutes) Then the system engages backpressure by scaling workers, rate-limiting provider pull/subscriptions if supported, and continues returning 2xx to webhooks after enqueue succeeds Given an operator triggers a DLQ replay When executed Then messages are reprocessed respecting idempotency and original ordering constraints
Periodic Reconciliation to Heal Drift
Given periodic reconciliation is scheduled When it runs hourly for incrementals and nightly for full scans Then the system compares provider state (roles, groups, entitlements) to local state and repairs divergences without manual intervention Given drift is detected for a subject When reconciled Then Gatekeeper policies match provider truth and an audit entry links the drift, fix, and source snapshot ids Given reconciliation cannot complete within its SLA (e.g., >30 minutes for nightly) When detected Then it is resumable from checkpoints and emits progress metrics (processed, corrected, skipped, errors)
SCIM 2.0 Provisioning Compliance
Given a SCIM 2.0 request (Users/Groups create, update, delete, patch) with valid OAuth and schemas When received Then the service validates payloads, applies changes idempotently, and returns appropriate 2xx responses with ETags Given a SCIM PATCH with add/remove operations to entitlements When processed Then Gatekeeper policies reflect the change and are visible in API/UX within 5 minutes (p95) Given an invalid SCIM attribute or precondition failure When processed Then the service returns 400/412 with SCIM error payload (scimType, detail) and no partial side effects are applied
Attribute Mapping & Transformation Studio
"As an ops manager, I want to define how IdP groups and CRM plans map to Timeglue roles and booking entitlements so that our policies match our org model."
Description

Offer an admin UI to map external attributes (groups, roles, plans, entitlements) to Timeglue roles and Gatekeeper policy attributes. Include transformation functions (normalization, regex, list ops, conditional expressions), default values, and lookup tables for CRM plan-to-tier mapping. Provide versioned configurations, change previews, validation, and sample-payload testing with dry-run results before applying to production.

Acceptance Criteria
Create and activate a basic attribute mapping
Given an admin with Mapping:Write permissions and connected sources (Okta, Salesforce) When the admin maps external.group = "team-leads" to Timeglue.role = "Team Lead" and Gatekeeper.policy.canSchedule = true and saves as Version 1 (Draft) with a change note Then Version 1 is persisted with versionId, author, timestamp, and note, appears in the Version list as Draft, and no production changes occur until activation
Execute transformations: normalization, regex, list ops, conditional expressions
Given a transformation pipeline using functions [trim, lowerCase, upperCase, regexMatch, regexExtract, split, join, contains, any, all, unique, map, if, equals, in, and, or, not] When the admin configures lowerCase(trim(external.title)), regexExtract(external.email, "^(.*)@example.com$"), contains(split(external.tags, ","), "vip"), and conditional if(external.country == "US" and contains(external.deptCodes, "OPS"), "Ops-US", "Other") and tests with a sample payload Then each step evaluates deterministically, intermediate values are displayed in order, and the final output matches expected values within 2 seconds per sample
Apply default values when attributes are missing or empty
Given a mapping sets Timeglue.role defaultValue = "Contractor" when external.role is missing or empty and treats null and empty distinctly per admin selection When a sample payload with external.role = null and another with external.role = "" are tested Then the configured behavior applies: the chosen case uses "Contractor" as fallback and the other follows pass-through; when external.role = "Manager" is present, the default is not applied; preview annotates fields with reason = "defaultApplied" when used
Map CRM plan to tier via lookup table
Given a lookup table "CRM Plan → Tier" with rows [("Pro Annual" → "Pro"), ("Starter" → "Starter")] and fallback = "Starter" and case-insensitive matching enabled When a payload has crm.plan = "Pro Annual" Then the resolved tier is "Pro" When a payload has crm.plan = "legacy x" Then the resolved tier is "Starter" via fallback And the admin can add/edit/delete rows and import/export CSV; any change updates the current Draft version and appears in the diff
Versioning, validation, activation, and rollback
Given Active Version = 3 and Draft Version = 4 with modified mappings and lookup rows When the admin clicks Validate on Version 4 Then validation reports 0 errors and <= 10 warnings, with exact locations; Activate remains disabled if any errors > 0 When the admin activates Version 4 Then Version 4 becomes Active within 10 seconds, Version 3 becomes Inactive, and all subsequent sync evaluations use Version 4 When the admin re-activates Version 3 Then rollback succeeds within 10 seconds and an audit log records actor, timestamp, fromVersion, toVersion, and changeNote
Sample-payload dry-run with change preview
Given the admin opens Test & Preview on Draft Version 4 and loads 1–10 sample payloads (JSON) from connected sources or uploads files When the admin runs Dry-Run Then no production writes occur, a diff view shows before→after values for roles, tiers, and Gatekeeper policy attributes, matched rules, applied transforms, defaults used, and lookup hits; results are exportable as CSV; dry-run on up to 1,000 records completes within 60 seconds or streams paginated results with progress
Real-time application to Gatekeeper upon external updates
Given Active Version 4 When an external update changes a user’s Okta group or CRM plan Then the mapping evaluates and updates Gatekeeper policy attributes and Timeglue roles within 30 seconds end-to-end And repeated identical events produce no duplicate side effects (idempotent) And transient failures retry up to 3 times with exponential backoff; persistent failures are logged with error codes and surfaced in the admin Activity log without blocking other updates
Source Precedence & Conflict Resolution
"As a platform admin, I want to control which system is authoritative for a given attribute so that conflicting updates don’t corrupt policies."
Description

Enable per-attribute source authority and merge strategies when multiple systems supply overlapping data. Support ordered precedence lists, last-write-wins with timestamp tolerance, staleness TTLs, and explicit overrides. Detect and prevent cyclical updates between systems, annotate records with provenance, and expose effective values to Gatekeeper with reasoning metadata for transparency and troubleshooting.

Acceptance Criteria
Per-Attribute Source Precedence Order
Given attribute "role" is configured with precedence [Okta, Azure AD, Salesforce] And Okta supplies "Engineer" and Salesforce supplies "Developer" and Azure AD has no value When Tier Sync evaluates the attribute Then the effective value for "role" equals "Engineer" And metadata.source_used = "Okta" And metadata.evaluated_sources = [ {source:"Okta", status:"selected"}, {source:"Azure AD", status:"missing"}, {source:"Salesforce", status:"lower_precedence"} ] Given attribute "role" is configured with precedence [Okta, Azure AD, Salesforce] And Okta has no value and Azure AD supplies "Contractor" When Tier Sync evaluates the attribute Then the effective value for "role" equals "Contractor" And metadata.source_used = "Azure AD"
Last-Write-Wins with Timestamp Tolerance
Given attribute "account_tier" uses merge strategy = last_write_wins and tolerance_seconds = 120 and precedence = [Okta, Salesforce] And Salesforce provides "Business" with updated_at = 2025-09-01T10:00:30Z And Okta provides "Pro" with updated_at = 2025-09-01T10:01:20Z When Tier Sync evaluates the attribute Then the effective value equals "Pro" And metadata.strategy = "last_write_wins" And metadata.tolerance_applied = false Given attribute "account_tier" uses merge strategy = last_write_wins and tolerance_seconds = 120 and precedence = [Okta, Salesforce] And Okta provides "Pro" with updated_at = 2025-09-01T10:00:10Z And Salesforce provides "Business" with updated_at = 2025-09-01T10:01:00Z (difference = 50s within tolerance) When Tier Sync evaluates the attribute Then the effective value equals "Pro" (precedence breaks near-simultaneous tie) And metadata.tolerance_applied = true And metadata.reason = "within_tolerance_precedence_applied"
Staleness TTL Enforcement and Fallback
Given attribute "manager_id" has staleness_ttl_hours = 24 And Okta value = "123" with updated_at = now-48h And Salesforce value = "456" with updated_at = now-2h When Tier Sync evaluates the attribute Then the effective value equals "456" And metadata.evaluated_sources includes {source:"Okta", status:"stale_ttl_exceeded"} and {source:"Salesforce", status:"selected"} Given attribute "manager_id" has staleness_ttl_hours = 24 And all contributing sources have updated_at older than 24h When Tier Sync evaluates the attribute Then no effective value is emitted to Gatekeeper for "manager_id" And metadata.state = "no_current_source" And metadata.reasons include ["all_sources_stale"]
Explicit Admin Overrides with Audit and Expiry
Given an explicit override exists for user "u_123" on attribute "account_tier" = "Enterprise" with expires_at = 2025-12-31T23:59:59Z and created_by = "admin_42" And Okta provides "Pro" and Salesforce provides "Business" When Tier Sync evaluates the attribute Then the effective value equals "Enterprise" And metadata.override.active = true and metadata.override.created_by = "admin_42" and metadata.override.expires_at = "2025-12-31T23:59:59Z" And metadata.evaluated_sources include statuses "overridden" Given the current time is 2026-01-01T00:00:01Z (after expiry) When Tier Sync next evaluates the attribute Then the override is ignored (metadata.override.active = false) And normal merge rules determine the effective value
Cyclical Update Detection and Suppression
Given an outbound write to Salesforce for user "u_123" attribute "role" = "Manager" occurs at 2025-09-01T10:00:00Z with change_id = "C123" And an inbound change from Salesforce for the same user and attribute arrives at 2025-09-01T10:01:00Z carrying provenance.change_id = "C123" When Tier Sync processes the inbound change Then the inbound change is ignored as self-originated within suppression_window_hours = 24 And no outbound write is produced as a result of this inbound event And an audit entry is recorded with {event:"cycle_suppressed", change_id:"C123", user:"u_123"} Given a chain Okta -> Salesforce -> Okta where the inbound Okta event carries provenance originating_from = "Timeglue" and change_id propagated When Tier Sync processes the event Then the update is suppressed and the cycle terminates with audit reason = "cycle_detected"
Provenance and Reasoning Metadata Exposed to Gatekeeper
Given Gatekeeper requests the effective user profile via the Tier Sync API for user "u_123" When the response includes attribute "account_tier" Then the payload contains metadata: {source_used, strategy, strategy_params (precedence, tolerance_seconds, ttl_hours), source_timestamps, evaluated_sources [ {source, status, reason} ], decision_time, correlation_id} And correlation_id links to an audit log entry retrievable via the Audit API And the response conforms to schema version v1.2.0 with JSON Schema validation passing And sensitive source-specific identifiers are omitted except where required by schema (PII-safe)
Policy Auto-Enrollment & Deprovisioning
"As a team lead, I want people to gain or lose scheduling permissions automatically when they change teams or tiers so that we avoid misrouted or after-hours bookings."
Description

Automatically add or remove users from Gatekeeper policies and entitlements based on synced attributes, updating booking permissions, meeting lengths, time windows, and after-hours protections. Apply changes atomically to avoid transient access, backfill missing enrollments, and immediately deprovision on termination or tier downgrade with optional grace periods. Emit user and admin notifications for impactful changes and ensure policy evaluations are consistent across web and API.

Acceptance Criteria
Auto-Enrollment on Role/Tier Change
Given a user’s synced attributes newly match Policy A mapping rules When a change event is ingested from the IdP/HRIS/CRM (e.g., role=Manager or account_tier=Pro) Then the user is enrolled into Policy A within 30 seconds p95 and 2 minutes p99 And the user’s booking permissions, max meeting length, allowable time windows, and after-hours protections from Policy A take effect immediately across web and API And an audit log entry is created with correlation_id, source_system, old_policy_ids, new_policy_ids, and effective_at And the operation is idempotent such that replaying the same event does not duplicate enrollments or notifications
Atomic Policy Application Without Transient Access
Given a user transitioning from Policy A to Policy B due to an attribute change When the system applies the new enrollment Then all entitlements (permissions, meeting length, time windows, after-hours protections) update atomically in a single versioned snapshot And no request observes a mixed state; requests before commit use Policy A, requests after commit use Policy B And concurrent scheduling requests during the update either all evaluate under Policy A or under Policy B; no partial approvals occur And there is no interval where after-hours protections are disabled
Immediate Deprovision and Grace Period Handling
Given a user is terminated in the HRIS or their tier is downgraded When the terminating/downgrade event is processed Then all Gatekeeper policy enrollments and entitlements for the user are revoked within 30 seconds p95 and 2 minutes p99 (unless a grace period is configured) And new booking creation attempts are denied across web and API with a clear deprovisioned error code And if a policy-level grace_period_hours > 0 is configured for downgrades, deprovision occurs exactly at the grace period end; before that time, the prior entitlements remain in effect And user and admin notifications are emitted for the change with details (user_id, old_policy_ids, new_policy_ids, reason, effective_at)
Backfill Missing Enrollments on Sync
Given users whose attributes match Policy A mapping rules but are not currently enrolled due to prior missed sync When the next sync or scheduled reconciliation job runs Then all eligible users are enrolled into Policy A without requiring manual action And the process is idempotent; rerunning the job produces no additional changes And a reconciliation report is generated listing added users, skipped users with reasons, and timestamps And no users are enrolled if they no longer match at evaluation time
Consistent Policy Evaluation Across Web and API
Given a specific user and meeting request parameters When the request is evaluated via the Web UI and via POST /api/v1/schedules/validate Then both interfaces return identical decisions for allow/deny, maximum meeting length, permissible time windows, and after-hours constraints And both responses include the effective policy_id and version used for evaluation And parity is maintained regardless of concurrent enrollment changes by using the same committed policy snapshot
Notifications for Impactful Changes
Given a user’s policy enrollment is added, removed, or changed (including termination and tier downgrade) When the change is committed Then a user notification is sent within 60 seconds if the change affects booking permissions, meeting length, time windows, or after-hours protections And an admin notification is sent to configured channels (email, Slack, webhook) with user_id, old_policy_ids, new_policy_ids, effective_at, reason, and source_system And duplicate notifications are suppressed for retried or replayed events And notification delivery failures are retried with exponential backoff for at least 24 hours and surfaced in admin logs
Real-Time Sync Reliability and SLOs
Given a sequence of user attribute changes A→B→C from an external system with potential retries or out-of-order delivery When events are processed by Tier Sync Then the final applied enrollment state reflects C (the latest authoritative state) and earlier states are not re-applied And 95% of attribute changes are reflected in effective policy within 30 seconds and 99% within 2 minutes under normal operating conditions And duplicate or replayed events are de-duplicated using an event_id/etag to ensure idempotent outcomes And violations of the propagation SLO are recorded with metrics and error logs for alerting
Audit Trail & Change History
"As a compliance officer, I want a complete history of sync-driven changes so that we can demonstrate who got access and why."
Description

Record immutable logs for every sync and policy-affecting change, including source system, event type, actor/system, timestamps, before/after values, applied mappings, and precedence decisions. Provide searchable UI and exportable APIs, scheduled compliance reports, and integrations for Slack/email digests. Enforce retention policies and access controls to meet audit and regulatory requirements.

Acceptance Criteria
Immutable logging and integrity verification
- Given any sync or policy-affecting event occurs, when the event is recorded, then an append-only log entry is written with event_type, source_system, actor_id (or system), correlation_id, occurred_at (UTC ISO-8601), received_at, and request_id. - Given a policy-affecting event, when it is recorded, then the entry includes before_values and after_values at field-level granularity. - Given any log write, when stored, then the entry is content-addressed (SHA-256) and hash-chained to the previous entry; a tamper verification endpoint returns chain_status=valid for the last 10,000 entries. - Given a log entry is created, then it cannot be updated or deleted by any API (WORM); attempts to mutate return 405.
Sync event audit with source attribution and outcomes
- Given a sync job executes (Okta/Azure AD/Salesforce, etc.), when it completes or fails, then a log entry captures connector_id, job_id, status (success/partial_failure/failure), started_at, finished_at, duration_ms, records_processed, records_added, records_updated, records_deleted, error_code and error_message (if any), retry_count, and idempotency_key. - Given a partial failure, when per-record outcomes are available, then the log includes a per_record summary with counts by outcome. - Given the audit API/UI is queried by source_system and time range (last 30 days), when executed, then the matching sync events return within p95 <= 2s with correct totals.
Policy change capture with mappings and precedence explanations
- Given a Gatekeeper policy change is triggered (auto-sync or manual), when the change is applied, then the audit entry includes policy_id, rule_id, origin (auto_sync/manual), actor_id, before_values, after_values, applied_mapping_ids, precedence_rule_evaluations, winner_source, effective_at, and impacted_subjects_count. - Given the change is viewed in the Audit UI, when the record is opened, then a human-readable diff is rendered and a precedence explanation section lists evaluated rules and the winning condition.
Audit UI search, filter, sort, and pagination
- Given an authorized user opens the Audit UI, when they apply filters (date range, event_type, source_system, actor_id, policy_id, status), then results reflect filters correctly, are sortable by occurred_at and actor_id, and paginate with page sizes 25/50/100. - Given a query over the last 90 days with <=100k results, when executed, then p95 response time <= 1.5s and memory usage within the browser remains under 200MB. - Given a result is selected, when the detail view opens, then it displays all captured fields including before/after diffs (if applicable) and raw JSON payload.
Audit export APIs with filtering, pagination, and security
- Given a client with audit.read scope and valid token, when it calls GET /v1/audit/events with filters (time_from, time_to, event_type, source_system, actor_id, policy_id, status) and pagination (cursor, limit<=1000), then it receives 200 with a versioned schema, items[], and next_cursor. - Given high-volume export is requested, when POST /v1/audit/exports is called, then an async job is created producing CSV and NDJSON artifacts (gzip) within the requested time range, with a pre-signed URL that expires in <=24h. - Given API usage exceeds 60 requests/min per token, when continued, then 429 is returned with retry-after; unauthorized or insufficient scope returns 401/403 respectively.
Scheduled compliance reports and digest delivery
- Given a compliance schedule (weekly/monthly, timezone) is configured, when the period closes, then a report is generated within 10 minutes containing: counts by event_type, failures, policy changes by origin, top actors, and SLO metrics. - Given delivery channels are configured (email, Slack), when the report is generated, then recipients receive an email with attached PDF and CSV and a Slack message to the designated channel with a link to the report; delivery failures are retried 3 times and logged. - Given a report is generated, when accessed later, then it is retrievable from the Reports UI/API for at least 13 months.
Access controls, redaction, and retention enforcement
- Given role-based access is configured, when a user without audit:read permission attempts to access the Audit UI/API, then access is denied (403) and logged; users with limited scope see only events for permitted accounts/tenants. - Given a viewer lacks clearance for sensitive fields, when viewing records, then configured fields (e.g., email, external IDs) are masked or redacted; full content remains available to authorized roles. - Given a retention policy (e.g., 400 days default) is set, when entries exceed the retention period and are not on legal hold, then they are purged by scheduled jobs; purge events are logged, legal_hold=true prevents deletion, and storage usage reflects the purge within 24h.
Admin Console & Health Monitoring
"As an admin, I want to monitor sync health and address failures quickly so that our scheduling policies stay accurate."
Description

Provide a consolidated console showing connector status, last successful sync, lag, throughput, and error rates per source. Include manual test sync, reprocess from checkpoint, dead-letter inspection, and secrets rotation. Surface alerts via email/Slack/webhook for failures or excessive drift, and expose operational metrics (latency, queue depth, webhook failures) for observability and on-call readiness.

Acceptance Criteria
Connector Health Overview Dashboard
Given multiple configured sources (e.g., Okta, Azure AD, Salesforce) When an admin opens the Admin Console Then each source row displays provider, environment, status (Healthy|Degraded|Failed), last successful sync timestamp (ISO 8601 UTC), sync lag (minutes), 15‑minute throughput (events/sec), and 15‑minute error rate (%) And status badges render as Green for Healthy (<1% errors, lag < 5 min), Yellow for Degraded (1–5% errors or lag 5–30 min), Red for Failed (>5% errors or lag > 30 min or last sync > 60 min ago) And the table supports sort by any column and filter by status/provider And data auto‑refreshes every 30 seconds and shows a staleness indicator if data age > 60 seconds And client‑side sort/filter operations complete within 200 ms for up to 500 connectors
Manual Test Sync (Dry Run)
Given a selected connector When the admin clicks "Test Sync" Then a dry‑run job is enqueued and executes without mutating Gatekeeper policies or entitlements And the UI displays a summary within 15 seconds containing counts of potential adds/updates/deletes and top 10 sample changes And permission or rate‑limit errors are surfaced with actionable messages and correlation ID And the action is rate‑limited to 1 concurrent dry‑run per connector; subsequent attempts show a disabled state with retry time
Reprocess From Checkpoint
Given a connector and a valid checkpoint (timestamp or bookmark) When the admin initiates "Reprocess" Then events from the checkpoint to now are reprocessed with exactly‑once semantics (idempotent upserts; dedupe by event ID) And progress is visible (percentage, current window, ETA) and updates at least every 5 seconds And partial failures are retried with exponential backoff up to 3 attempts before DLQ And upon completion, last successful sync time and lag are updated and no duplicate entitlements are created And an invalid checkpoint is rejected with a validation error before job creation
Dead‑Letter Queue Inspection & Retry
Given dead‑letter items exist for a connector When the admin opens "Dead Letters" Then a paginated list shows created time, error code, redacted payload hash, last attempt time, and attempt count And opening an item shows the full error, stack trace, and redacted payload fields with PII masked And the admin can retry a single item or a selected batch; successful retries are removed from DLQ; failures remain with incremented attempt count And the admin can export the current filtered view to CSV or JSON And all actions are recorded in the audit log with actor, timestamp, and item IDs
Secrets Rotation Without Downtime
Given a connector with active credentials When the admin initiates "Rotate Secret" and submits new credentials Then the credentials are validated against the provider before activation And dual‑use mode keeps old and new credentials valid for up to 10 minutes to avoid auth failures during in‑flight jobs And after successful validation, the new secret becomes primary, the old secret is revoked and securely destroyed, and an audit log entry is created with masked fields And if validation fails, no changes are applied and a clear error message is shown And rotation can be rolled back to the previous secret within the dual‑use window
Alerting for Failures and Drift
Given alert destinations are configured (email, Slack, webhook) When a connector experiences a failed sync, a sustained error rate > 5% for 15 minutes, or entitlement drift > 2% of scoped users for 10 minutes Then an alert is sent within 60 seconds including connector, metric, threshold, current value, timestamp, and runbook link And alerts de‑duplicate within a 30‑minute window and send resolve notifications when the metric stays below threshold for 10 minutes And a "Send Test Alert" action delivers to each destination and records delivery status (2xx success or error)
Operational Metrics for Observability
Given an authenticated metrics endpoint When scraped by Prometheus Then metrics are exposed for p50/p95/p99 sync latency (seconds), queue depth per connector, webhook failure rate (%), job throughput (events/sec), and DLQ size And each metric includes labels connector_id, provider, environment, and region; total time‑series count per metric does not exceed 10,000 And SLO recording rules exist: ≥99.5% of sync jobs complete < 5 minutes; webhook success rate ≥99% And metrics are retained for at least 14 days and reflect updates within 60 seconds

Quota Guard

Enforce per-account booking quotas with real-time counters, soft/hard limits, and configurable grace periods. Display remaining quota in-link, forecast depletion, and throttle or reroute requests before SLAs are at risk—preventing overcommit and protecting team bandwidth.

Requirements

Real-Time Quota Counter Engine
"As a workspace admin, I want quota usage to update in real time across all booking channels so that limits are enforced consistently without overbooking."
Description

Implements atomic, low-latency counters that track bookings per account, team, and user across all Timeglue entry points (links, embed, API). Supports fixed and rolling windows, sub-quotas by booking type, and multi-region consistency with idempotent increments and concurrency control. Integrates into the scheduling decision pipeline to evaluate remaining capacity before slot confirmation, with cached reads and write-through updates under a strict latency budget. Provides graceful degradation (read-only mode, sticky partials) and emits structured metrics for observability. Ensures reset cadences (daily/weekly/monthly) and holiday-aware windows align with Timeglue work hours and focus blocks.

Acceptance Criteria
Atomic Idempotent Increment Across Entry Points and Regions
Given an account with quotas enabled and idempotency keyed by bookingId And 100 concurrent booking requests with the same bookingId arrive within 500ms via link, embed, and API from two regions When the quota engine evaluates and applies increments Then account, team, and user counters each increase by exactly 1 (not 100) And no duplicate increments are recorded for the same bookingId And the capacity decision path (read + evaluate) completes in ≤ 40ms p95 and ≤ 120ms p99 intra-region over 1,000 trials And a subsequent read from any region reflects the same counter values within ≤ 1s p95 and ≤ 3s p99 of the write And processing 1,000 unique bookingIds concurrently results in exactly 1,000 successful increments with zero lost or double counts
Fixed and Rolling Window Quotas with Holiday/Work-Hours Alignment
Given an account in America/Los_Angeles with daily=50, weekly=200, monthly=800, and rolling_30d=1,000 quotas And workday start is 09:00 local and organization holidays are configured When the clock passes 09:00 on a non-holiday Then the daily window resets at 09:00 local time When the scheduled reset falls on a configured holiday Then the daily reset defers to the next non-holiday 09:00 local time without carrying over consumed units And weekly resets occur at 09:00 Monday local; monthly resets occur at 09:00 on the 1st local And rolling_30d computations use account local time boundaries and include all bookings in the last 30x24h regardless of hour And counters never reset mid-window and never drift negative
Booking-Type Sub-Quota Enforcement in Decision Pipeline
Given an account with root daily quota=100 And sub-quotas: Sales Demo=60/day, Support Call=50/day When 60 Sales Demo bookings are confirmed in the current daily window Then the 61st Sales Demo booking is rejected with reason=sub_quota_exhausted while Support Call bookings remain allowed if root quota permits When root quota is exhausted but a sub-quota still has capacity Then further bookings of any type are rejected with reason=root_quota_exhausted And each confirmed booking decrements both its type sub-quota and the root quota atomically And reads of remaining capacity for each scope and type complete in ≤ 25ms p95 from cache
Soft and Hard Limits with Configurable Grace Periods
Given an account with soft_limit=90/day, hard_limit=100/day, grace_units=10, grace_duration=2h When usage reaches 90 in the current daily window Then the decision response includes soft_limit_reached=true and booking remains allowed When usage reaches 100 within the window Then up to 10 additional bookings are allowed within the next 2h with decision=grace_allow and remaining_grace decremented per booking And after grace_units are exhausted or grace_duration expires (whichever first), further bookings are rejected with decision=hard_block And all responses include accurate remaining totals for root and sub-quotas and remaining_grace And no more than 10 grace bookings are accepted in any rolling 2h period following the first hard_limit breach
Cached Reads and Write-Through Consistency Under Latency Budget
Given hot accounts with active traffic and a cache TTL of 3s When checking remaining capacity during scheduling Then cache-hit read + evaluation latency is ≤ 20ms p95 and ≤ 50ms p99 over 5,000 requests When a booking is confirmed and counters are incremented Then the write-through updates the cache and subsequent reads reflect the new values within ≤ 100ms p95 And cache misses fall back to the store with ≤ 120ms p95 latency and ≤ 0.1% error rate over 10,000 requests And no stale read is served older than TTL unless in degradation mode (tracked via metric=stale_read)
Graceful Degradation: Read-Only Mode and Sticky Partials
Given the primary data store becomes unavailable or exceeds error threshold for 30s When the engine enters degradation mode Then it serves read-only decisions using last-known counters (sticky partials) for up to 60s TTL per account And all increment attempts return decision=degraded_read_only with HTTP 503 retry-after=5s and no counter mutation And the system emits events degradation_started and degradation_recovered with correlation ids And upon recovery, counters resynchronize and idempotent retries for prior bookingIds do not double-increment And during degradation, stale_read metric increments and error budgets are tracked
Structured Metrics, Tracing, and Alerting for Quota Operations
Given quota operations (read, evaluate, increment, replicate, dedupe) When the system runs under normal and stress conditions Then it emits structured metrics: qps, cache_hit_rate, read_latency_ms[p50,p95,p99], write_latency_ms[p50,p95,p99], replication_lag_ms[p95,p99], dedupe_count, conflict_retries, error_rate, stale_read_count per account/team/user And each decision log includes correlation_id, idempotency_key, account_id, team_id, user_id, scope, window_type, before/after counts, and decision outcome with no PII And alerts fire when read_latency_ms_p95 > 40ms for 5m, replication_lag_ms_p95 > 1,000ms for 5m, or error_rate > 0.5% for 5m And all metrics are available in the observability backend within ≤ 15s of emission
Configurable Quota Policies & Grace Periods
"As a billing owner, I want to configure soft and hard limits with grace periods per plan so that my team can absorb short spikes without breaking SLAs."
Description

Delivers a policy engine and admin UI to define per-plan and per-resource quota rules, including soft vs. hard limits, grace period durations, burst allowances, and reset schedules. Policies can be scoped to account, team, or booking type and include exceptions for holidays and maintenance windows. Validates configurations, supports versioning and safe rollbacks, and propagates updates without downtime. Integrates with billing/plan tiers and maps policy outcomes to user-facing messages and HTTP responses.

Acceptance Criteria
Soft vs. Hard Limits Per Plan & Resource
Given an admin opens Quota Policies UI for Plan "Pro" and Resource "booking" When they set soft_limit=100 and hard_limit=120 Then the form validates and Save is enabled Given soft_limit is >= hard_limit or any limit is non-positive When saving Then validation blocks save with messages "Soft limit must be less than hard limit" and "Limits must be positive integers" Given a saved policy for an account on Plan "Pro" When a booking is created and current_count < soft_limit Then the request is allowed and the counter increments by 1 Given current_count >= hard_limit and no active burst When a booking is created Then the request is rejected with HTTP 429 and error_code="QUOTA_HARD_LIMIT" and no counter change
Grace Period and Burst Allowance Enforcement
Given a policy with soft_limit=100, hard_limit=120, grace_period=72h, burst_allowance=10 When the 100th booking is accepted at time t0 Then a grace window [t0, t0+72h) is started and quota_state="grace" Given t in [t0, t0+72h) and total_count <= (hard_limit + burst_allowance) When a booking request arrives Then it is allowed and response is flagged with quota_state="grace" Given total_count > (hard_limit + burst_allowance) during grace When a booking request arrives Then it is rejected with HTTP 429 and error_code="QUOTA_BURST_EXHAUSTED" Given t >= t0+72h and current_count >= soft_limit When a booking request arrives Then it is rejected with HTTP 429 and error_code="QUOTA_GRACE_EXPIRED" Given burst_allowance=0 and t in [t0, t0+72h) When current_count < hard_limit Then requests are allowed until hard_limit is reached
Reset Schedules and Counter Resets
Given a policy with reset_schedule="monthly" anchored to the account's billing cycle day at 00:00 in account_time_zone When the anchor timestamp passes Then all counters for that scope reset to 0 within 60 seconds Given a policy with reset_schedule="weekly" anchored to Monday 00:00 in account_time_zone When the anchor timestamp passes Then counters reset to 0 within 60 seconds Given counters are reset When the next booking is created Then current_count becomes 1 and remaining_quota reflects the new cycle limits
Scoped Policy Resolution (Account, Team, Booking Type)
Given policies exist at Plan (P), Account (A), Team (T), and BookingType (B) scopes When evaluating a booking for B in T under A on P Then the selected policy is the most specific available with priority: BookingType > Team > Account > Plan Given no scoped policy exists for a booking When evaluating Then the Plan-level default policy is applied Given a Team-level policy is deleted When evaluating subsequent requests Then the system falls back to Account scope within 5 seconds
Exceptions for Holidays and Maintenance Windows
Given an exception window type="holiday" with exclude_from_count=true for region="US" on 2025-11-27 in account_time_zone When bookings are created on that date Then they are allowed and do not increment quota counters Given an exception window type="maintenance" with enforcement_override="block" from 01:00–02:00 UTC When a booking is attempted during the window Then it is rejected with HTTP 503 and error_code="MAINTENANCE_WINDOW" Given overlapping exception windows When evaluating Then the most restrictive enforcement_override applies and exclude_from_count=true is honored if any matching exception sets it
Policy Versioning, Safe Rollback, and Zero-Downtime Propagation
Given version v1 is active and an admin publishes v2 with effective_at=now When published Then v2 is applied to new requests within 5 seconds and in-flight requests continue under v1 Given v2 yields undesired outcomes When the admin performs a rollback to v1 Then the switch is atomic, completes within 5 seconds, and no requests are dropped Given any policy change When audited Then an immutable audit record with actor, diff, version_id, and effective_at is stored Given a multi-region deployment When a policy version is activated Then all regions report the same active version_id within 5 seconds
Billing Tier Integration and Response/User Messaging Mapping
Given an account upgrades from Plan "Starter" to "Pro" When billing confirms the new plan Then the corresponding "Pro" quota policy is applied within 60 seconds and remaining_quota is recalculated Given a request is allowed while in grace When responding Then API returns HTTP 200 with headers: X-Quota-Limit, X-Quota-Remaining, X-Quota-State="grace", and X-Grace-Expires-At, and the booking link displays "In grace: N over soft limit" Given a request is throttled per policy outcome="throttle" When responding Then API returns HTTP 429 with Retry-After set per policy and error_code="QUOTA_THROTTLED" Given a request is blocked per hard limit or grace expiry When responding Then API returns HTTP 429 and UI copy is populated from the policy's user_message template
In-Link Quota Display
"As an invitee, I want to see how many slots remain and when availability resets so that I can decide whether to book now or later."
Description

Surfaces remaining quota, percent used, and time-to-reset directly within public scheduling links and embedded widgets. Provides a branded, accessible UI (A11y labels, color contrast) with optional progress bars and discreet badges. Localizes reset times, respects privacy settings (hide exact counts when configured), and degrades gracefully if data is temporarily unavailable. Emits analytics events (impressions, clicks) and ensures sub-150ms add-on latency to preserve conversion.

Acceptance Criteria
Default In-Link Quota Readout (Count, % Used)
- Given an account has quota_limit=100 and used=68 and display_style="progress_bar" and privacy_mode="show_exact", When a public scheduling link or embedded widget loads, Then the UI renders "32 remaining" and "68% used" within the quota component in both link and embed contexts. - Then percent_used equals round((used/quota_limit)*100) bounded to [0,100], and remaining equals max(quota_limit - used, 0). - Then the progress bar fill matches percent_used and includes an accessible text label reflecting the same values. - Then the component uses the account’s theme tokens for colors and typography; if unavailable, it falls back to the product defaults without visual breakage. - Then toggling display_style to "badge" shows a discreet badge with the same values and hides the progress bar.
Localized Time-to-Reset Rendering
- Given quota_reset_at is provided in UTC and the viewer has timezone and locale available, When the link or embed renders, Then the component displays time-to-reset localized to the viewer’s timezone. - Then if time_to_reset < 48 hours, show a relative string (e.g., "Resets in 3h 15m") that updates at least every 60 seconds; else show an absolute localized datetime (e.g., per viewer locale) with timezone abbreviation where available. - Then the tooltip or accessible name includes the absolute datetime in localized format regardless of relative/absolute display mode. - Then daylight saving transitions are correctly reflected (no off-by-one-hour errors) as validated against the IANA timezone database.
Privacy Mode: Hide Exact Counts
- Given privacy_mode="hide_exact", When the component renders, Then numeric counts and exact percentages are not displayed in the UI or exposed in DOM attributes or accessible names. - Then the UI shows a discreet status badge with one of: "Healthy" (remaining >= 50%), "Low" (10% ≤ remaining < 50%), or "Critical" (remaining < 10%). - Then tooltips and analytics payloads must not include exact remaining or used counts; only the categorical status and percent bucket are permitted. - Then privacy_mode overrides display_style and forces the discreet badge (no progress bar).
Accessible UI Compliance (WCAG 2.1 AA)
- Given the quota component is rendered, When evaluated with WCAG 2.1 AA, Then text and essential icons meet a contrast ratio ≥ 4.5:1, and large text (≥18pt/14pt bold) ≥ 3:1. - Then all interactive affordances (info icon, tooltip trigger) are keyboard reachable with visible focus indicators and operable via Enter/Space. - Then screen readers announce an accessible name such as "Quota status: 32 remaining, 68 percent used. Resets in 3 hours" (or the privacy-safe equivalent), and the countdown updates are exposed via aria-live="polite" without causing focus loss. - Then information is not conveyed by color alone; status is also provided via text labels. - Then the component is navigable in embed contexts within iframes without trapping focus.
Graceful Degradation on Data Unavailability
- Given the quota service responds with 5xx, 4xx, or times out after 800ms, When the component renders, Then it shows a neutral "Quota unavailable" badge with a retry affordance and does not block booking actions. - Then if a cached value not older than 5 minutes exists, it is displayed with a "Last updated <n> min ago" indicator; if no cache, show skeleton for up to 800ms before fallback. - Then the UI does not throw uncaught exceptions, and the layout remains stable (CLS increase ≤ 0.02) during fallback. - Then the component retries in the background with exponential backoff (starting at 2s, max 30s) without janking the UI.
Analytics: Impressions and Interactions
- Given the component first becomes visible, When it renders, Then a "quota_display_impression" event is emitted within 100ms with fields: account_id, link_id|embed_id, viewer_tz, locale, privacy_mode, display_style, variant, percent_used_bucket, remaining_count (only if privacy_mode allows), reset_at, latency_ms. - Then clicking the quota info/badge or tooltip emits a "quota_display_click" event with the same context plus click_target. - Then duplicate impressions within the same page view are suppressed; re-renders do not create extra impressions. - Then events are queued offline and flushed when connectivity resumes; PII (e.g., email, name) is never included. - Then 95%+ of events are successfully delivered in supported browsers (Chrome, Firefox, Safari, Edge) as verified by delivery metrics.
Performance Budget: ≤150ms Add-on Latency
- Given the quota component is disabled vs enabled in otherwise identical conditions, When measured via real-user monitoring, Then the p95 incremental time from component mount to first contentful paint of quota values is ≤ 150ms and p50 ≤ 80ms on mid-tier devices and 5Mbps/50ms RTT networks. - Then the component performs network requests in parallel and uses a response timeout of ≤ 150ms before showing fallback to meet budget. - Then main-thread blocking work attributable to the component is ≤ 16ms per frame (no long tasks), and the component adds ≤ 0.02 to CLS. - Then in privacy or fallback modes, the same or better latency targets are met.
Depletion Forecasting & Threshold Alerts
"As a team lead, I want proactive alerts when we’re on track to hit our booking limit so that I can adjust capacity or upgrade before service is impacted."
Description

Forecasts quota depletion using recent consumption trends to estimate the date/time remaining under current velocity. Supports configurable alert thresholds (e.g., 80%, 95%, T-3 days) and channels (email, Slack, webhooks), with noise suppression, digests, and one-click plan upgrade prompts. Exposes forecasts and alert state in the account health dashboard and API, including confidence ranges and rationale for transparency.

Acceptance Criteria
Dashboard Forecast with Confidence Bands
Given an account with an active quota and at least 14 days of consumption data When the Account Health dashboard loads for that account Then the Forecast panel displays predicted_depletion_at in the account’s timezone (ISO 8601), and And shows 50% and 95% confidence intervals as start/end timestamps, and And shows remaining_units, average_daily_consumption, trend_window_days, and last_updated_at, and And values match the Forecast API within ±1 minute for times and ±1 unit for counts, and And if fewer than 3 data points exist, the panel displays an “Insufficient data” state and no depletion ETA
80% Threshold Slack Alert Dispatch
Given an 80% usage threshold is enabled with Slack channel configured and a 2-hour cooldown And the current percent_used is below 80% When consumption events raise percent_used to ≥ 80% for the first time within the cooldown window Then a Slack message is posted to the configured channel within 60 seconds containing account_id, percent_used (1 decimal), remaining_units, predicted_depletion_at, confidence_50/95, and a single-click Upgrade link, and And the alert is recorded with timestamp and message_id, and And no duplicate 80% Slack alert is sent again until cooldown expires or a higher threshold is crossed
T-3 Days Forecast Email Alert with Upgrade CTA
Given “T-3 days” forecast threshold alerts are enabled for email with a 24-hour cooldown And forecasted depletion ETA is > 72 hours away When the predicted_depletion_at moves to ≤ 72 hours from now under current velocity Then a single email is delivered to configured recipients within 5 minutes with subject containing the account name and “T-3 days”, and And the body includes current percent_used, remaining_units, average_daily_consumption, predicted_depletion_at, confidence_50/95, and a brief rationale summary (trend_window_days and data_points_used), and And the email includes a one-click Upgrade CTA link with account-scoped token and UTM parameters, and And no additional T-3 days email is sent until cooldown expires unless the threshold escalates (e.g., to 95%)
Noise Suppression and Threshold Escalation
Given alert thresholds at 80% and 95% with a 2-hour cooldown per threshold/channel When percent_used fluctuates across 80% multiple times within the cooldown Then only one alert per threshold/channel is sent within the cooldown, and And if percent_used later crosses 95%, the 95% alert is sent even if the 80% cooldown is still active (escalation allowed), and And descending crossings (e.g., 96% to 94%) do not trigger alerts unless specifically configured for recovery alerts
Daily Digest of Forecast Changes
Given a daily digest is enabled for 09:00 in the account’s timezone And two or more forecast-relevant changes (threshold crossings or ≥10% ETA change) occurred in the last 24 hours When the digest window closes at 09:00 Then a single digest is sent (email and/or Slack per configuration) summarizing latest percent_used, remaining_units, predicted_depletion_at with confidence_50/95, changes since prior digest, and any alerts suppressed by cooldown, and And no individual alerts suppressed by cooldown are retroactively sent outside the digest
Webhook Alert Delivery with HMAC and Retries
Given a webhook endpoint URL and shared secret are configured for threshold alerts When any configured threshold is crossed (percent or T-±days) in the ascending direction Then the system POSTs a JSON payload including event_id, event_type, account_id, threshold, channel, percent_used, remaining_units, predicted_depletion_at, confidence_50/95, and rationale to the endpoint, and And includes an X-Timeglue-Signature header (HMAC-SHA256 of the body with the shared secret), and And considers any 2xx response an acknowledgment, otherwise retries up to 3 times with exponential backoff (1m, 5m, 15m), and And marks the delivery status (success/failure) in alert_state and exposes it on the dashboard
Forecast and Alert State API Exposure
Given a valid API client with read scope for the account When the client GETs /v1/accounts/{id}/quota-forecast Then the response is 200 within 300ms p95 and includes JSON fields: predicted_depletion_at, confidence_50, confidence_95, remaining_units, average_daily_consumption, trend_window_days, data_points_used, rationale.summary, alert_state.last_sent_per_threshold_channel, and next_scheduled_digest_at, and And ETag caching is supported; If-None-Match returns 304 when unchanged, and And 401 is returned for unauthorized and 404 for unknown account_id
Throttling, Queueing & Reroute Rules
"As a scheduler, I want requests automatically throttled or rerouted when we near limits so that commitments stay within our bandwidth."
Description

Introduces pre-commit backpressure when approaching or exceeding limits: token-bucket throttling, short-lived request queues with TTL, and rerouting logic to alternative hosts or slots with available quota. Enforces fairness across concurrent requests, returns structured user messaging for soft-limit scenarios, and integrates with SLA guardrails to avoid after-hours or overcapacity bookings. Provides observability (rates, drops, reroutes) and configurable per-plan behaviors.

Acceptance Criteria
Token-Bucket Throttling Enforcement
Given an account with token_bucket.capacity=60 and refill_rate=1/sec and queue_size=0 When more than 60 booking-create requests arrive within any rolling 60-second window Then requests exceeding capacity return HTTP 429 code=rate_limited with header Retry-After equal to seconds until next token (±1s) and no booking is committed Given tokens are available When a request arrives Then it is processed without 429 and consumes exactly 1 token Given processed request counts are observed When measuring any rolling 60-second window per account Then processed_total ≤ capacity with tolerance 0 Given a request includes idempotency_key and previously received 429 When retried after Retry-After elapses and a token is available Then exactly one booking is created and no duplicates occur
Short-Lived Request Queue (TTL + FIFO)
Given queue_enabled=true, max_length=50, per_request_ttl=2s, ordering=FIFO When tokens are exhausted and new requests arrive Then requests are enqueued until max_length is reached Given a queued request When its wait_time exceeds per_request_ttl Then it is dropped with HTTP 503 code=queue_ttl_expired within 50ms and no booking is committed Given the queue is full When a new request arrives without an available token Then return HTTP 503 code=queue_full within 50ms Given multiple requests are queued When tokens become available Then requests are dequeued strictly FIFO and under nominal load p95(queue_wait_seconds) ≤ 0.2s
Fairness Across Concurrent Requests
Given three clients A, B, C under the same account each submit 100 requests over 60 seconds When token bucket and queue are active Then each client receives between 30% and 40% of processed requests (variance ≤ 10%) and no client experiences starvation gaps > 1s Given one client bursts at 5× the others When dequeuing from the queue Then per-client maximum consecutive dequeues ≤ 5 before another client's request is dequeued Given client identity is derived from api_key or client_id When identity is absent Then fairness falls back to per-connection source_ip
Reroute to Alternative Hosts/Slots with Available Quota
Given the primary host/slot has reached a hard limit for the requested window When an alternative host/slot within the same team/region has available quota and meets SLA guardrails (within working hours, not overcapacity) Then the request is rerouted within 200ms, processed successfully, and the response includes reroute=true, reroute_target, reroute_reason=quota_exhausted Given no compliant alternative exists When the primary is at hard limit Then return HTTP 409 code=no_capacity_within_guardrails and include suggested_alternatives (up to 3 next-available slots) Given rerouting occurred When the same request is retried with the same idempotency_key Then the reroute decision is deterministic and no duplicate bookings are created
Soft-Limit Structured Messaging and Retry Guidance
Given quota usage ≥ soft_threshold and < hard_limit for an account When a booking-create request is submitted Then respond with HTTP 429 code=soft_limit including fields: remaining_quota, retry_after_seconds, grace_period_seconds, estimated_depletion_time, suggested_slots; and do not commit a booking Given the user waits retry_after_seconds and retries When a token is available Then the booking proceeds with HTTP 201 and soft-limit messaging is suppressed Given grace_period_seconds > 0 and allow_soft_limit_grace=true When the user explicitly confirms proceeding within grace Then the booking is accepted with HTTP 201 and response includes grace_applied=true and grace_remaining updated
Observability: Rates, Drops, Reroutes Telemetry
Given the system is handling requests When metrics are scraped Then counters/gauges exist and are labeled by account_id, plan, outcome: requests_total, processed_total, throttled_total, queued_total, queue_dropped_total, rerouted_total, reroute_fail_total; and histograms: queue_wait_seconds, processing_latency_seconds Given throttle, queue_drop, or reroute events occur When the request is handled Then a structured log is emitted with correlation_id, account_id, plan, event_type, cause, result, token_bucket_state, queue_position (if any) Given plan SLOs are defined When measured daily Then p95(queue_wait_seconds) ≤ 0.2s for Pro and ≤ 0.4s for Basic and alerts fire if throttled_total/request_total > 5% for 5m
Per-Plan Configurable Behaviors and Overrides
Given plan=Basic When defaults are applied Then token_bucket.capacity=30/min, refill_rate=0.5/sec, queue_ttl=1s, reroute_enabled=false Given plan=Pro When defaults are applied Then token_bucket.capacity=60/min, refill_rate=1/sec, queue_ttl=3s, reroute_enabled=true, soft_threshold=90% Given an account-level override sets token_bucket.capacity=80/min When the config is saved Then the override takes precedence within 1 minute without service restart and an audit_log entry records actor, before, after, timestamp Given an invalid configuration (e.g., queue_ttl < 0) When validation runs Then the change is rejected with error details and the last-known-good configuration remains active
Quota Decision Audit Log
"As a compliance officer, I want an audit trail of quota evaluations and overrides so that we can investigate disputes and satisfy audits."
Description

Creates an immutable, searchable log of quota evaluations, decisions (allow, soft-warn, throttle, block), and policy changes with actor, timestamp, request ID, and contextual metadata. Supports retention policies, export to CSV/SIEM, fine-grained access controls, and links from booking records for end-to-end traceability. Enables filters by account, team, time window, and outcome to expedite investigations and compliance reviews.

Acceptance Criteria
Decision Log Entry Creation and Traceability
Given a booking request is evaluated by Quota Guard When a decision is produced (allow, soft-warn, throttle, or block) Then an audit log entry is appended within <= 200ms of the decision and before the response is returned And the entry includes at minimum: request_id, account_id, team_id, actor (service/user), actor_type, timestamp (ISO 8601 UTC), decision, decision_reason, policy_id, policy_version, current_counter, window_definition, limit_type (soft/hard), limit_value, grace_period_status, throttle_duration_ms (if applicable), reroute_target (if applicable), client_ip, user_agent, and correlation_id And the entry’s primary key is unique per decision (request_id + evaluation_id) and idempotent on retries And the associated booking record displays a deep link to its audit entries by request_id And the audit entry includes a backlink URL to the booking record, resolving successfully via API and UI
Quota Policy Change Logging and Version Linking
Given a quota policy is created, updated, or deleted When the change is committed Then a policy_change audit entry is appended capturing: policy_id, previous_version, new_version, change_type (create/update/delete), actor, actor_type, timestamp (ISO 8601 UTC), scope (org/account/team), rationale/description, and a structured diff of changed fields And effective_at is recorded for changes with delayed activation And subsequent decision entries reference policy_id and policy_version matching the policy in effect at evaluation time And from a decision entry, the linked policy_change entries are discoverable by policy_id/version And attempting to evaluate a request during a policy rollout with effective_at in the future records the pending policy version in context without misattribution
Audit Log Immutability and Tamper Evidence
Given an existing audit entry When a user or service attempts to update or delete it prior to retention expiry Then the operation is rejected with 403 and no mutation occurs And all audit entries are stored append-only with a per-entry content hash and a hash-chain linking previous_hash -> current_hash And a daily anchor hash is signed and stored, and an integrity verification endpoint returns the latest anchor and verification result When an integrity check is run over a selected time window Then the system returns PASS with proof material if no discrepancies are found When a corruption or mismatch is introduced in a non-prod environment Then the integrity endpoint returns FAIL, identifies the first mismatched entry, and emits a security alert event
Search, Filter, and Pagination Performance
Given >= 1,000,000 audit entries exist over the past 90 days When a user searches with filters (account_id, team_id, time window, decision outcome, actor, request_id, policy_id/policy_version, entry_type: decision|policy_change) Then results return within <= 2s P95 for a 30-day window and <= 4s P95 for a 90-day window And results are paginated with cursor-based pagination supporting next/previous and stable sort by timestamp desc, then request_id And filters are combinable and precisely applied (inclusive start, exclusive end on time ranges; UTC normalization) And empty-result queries return 200 with total=0 and no items And total_count is returned or a count_estimate flag when counts would exceed SLA
CSV and SIEM Export Correctness and Reliability
Given a user with export permission selects a time range and filters When exporting to CSV Then the file contains the selected columns with headers, UTF-8 encoding, RFC 4180 escaping, and timestamps in ISO 8601 UTC And the row count equals the number of matched entries, with chunked downloads for files > 100MB And the export job can be resumed on network failure without duplicating rows When exporting to a SIEM destination (HTTPS webhook or syslog over TLS) with a configured shared secret Then each event includes a stable event_id, HMAC signature, and delivery metadata And delivery uses retry with exponential backoff, max retry window configurable, and deduplication at the receiver via event_id And failures surface job status=FAILED with error details and do not block subsequent exports
Retention Policies and Legal Holds Enforcement
Given a tenant-configurable retention policy (30–730 days) is set for audit entries When an entry ages beyond the configured retention and is not under legal hold Then it is purged within 24h of eligibility, associated indexes are updated, and the purge is logged with counts and range And exports and searches cannot return purged entries When a legal hold is applied to a scope (org/account/team or request_id range) Then affected entries are excluded from purge until the hold is released, with hold reason, actor, and timestamp recorded And retention settings changes themselves are audit-logged with actor, old_value, new_value, and effective_at
Fine-Grained Access Controls and Data Scoping
Given role- and scope-based permissions are configured (e.g., Org Admin, Auditor, Team Lead, Support) When a user queries, views, or exports audit entries Then they can access only entries within their permitted scopes (org/account/team) and actions (read, export, configure retention) And attempts outside scope or permission are denied with 403 and are themselves audit-logged with actor, target, and reason And sensitive fields (e.g., client_ip, user_agent) are masked for roles without PII access while preserving searchability via hashed facets And access controls apply uniformly across API, UI, and export endpoints
Quota API & Webhooks
"As a developer, I want APIs and webhooks for quota state so that I can integrate Timeglue limits into our internal tooling and dashboards."
Description

Provides authenticated endpoints (REST/GraphQL) to retrieve current usage, remaining quota, window reset times, and forecast data, plus management endpoints for policy inspection. Emits webhooks for threshold crossings, policy updates, and forecast changes with idempotency keys and retry semantics. Includes OAuth scopes, pagination, rate limits, versioning, SDKs, and comprehensive docs to support partner and internal integrations.

Acceptance Criteria
Retrieve Quota Metrics via REST (OAuth-protected)
Given a valid OAuth 2.0 access token with scope "quota.read" for account A When GET /v1/accounts/{A}/quota is called with Accept: application/json Then respond 200 with JSON containing fields: usage.current (integer >= 0), quota.remaining (integer >= 0), window.resetAt (RFC3339 timestamp), forecast.depletionAt (RFC3339 timestamp or null), accountId == A Given the token lacks "quota.read" When the same request is made Then respond 403 with error.code == "forbidden.scope" and no quota data Given an invalid or expired token When the request is made Then respond 401 with WWW-Authenticate: Bearer error="invalid_token" Given a valid ETag from a prior 200 response When the request is made with If-None-Match set and no fields changed Then respond 304 with no body Given a nonexistent accountId When the request is made Then respond 404 with error.code == "not_found" Given normal load conditions When the endpoint is invoked Then p95 latency <= 300ms and uptime SLO >= 99.9%
Retrieve Quota Metrics via GraphQL
Given a valid OAuth 2.0 token with scope "quota.read" When POST /graphql with query: query($id: ID!){ account(id:$id){ id quota{ usage current remaining window{ resetAt } forecast{ depletionAt } } } } Then respond 200 with data.account.id == $id and all requested fields present and typed per schema Given the token lacks "quota.read" When the query is executed Then respond 403 with errors[0].extensions.code == "forbidden.scope" Given a query requesting a field not in the schema When executed Then respond 200 with errors[] describing the unknown field and no data for that field Given the query includes an operationName and variables When executed Then the server honors operationName and variables
Webhook Delivery: Threshold, Forecast, and Policy Update Events
Given a subscribed endpoint with a shared secret When usage crosses a configured threshold (e.g., 80%), or forecast.depletionAt changes by >= 5 minutes, or a quota policy is updated Then Timeglue sends an HTTPS POST within 60s with headers: X-Timeglue-Event ("quota.threshold.crossed"|"quota.forecast.changed"|"quota.policy.updated"), X-Timeglue-Signature (HMAC-SHA256), Idempotency-Key (UUID), X-Timeglue-Timestamp (RFC3339) and a JSON body containing eventId, accountId, occurredAt, and event-specific payload Given the same event is retried When the receiver previously returned non-2xx or timed out (>10s) Then the POST is retried with exponential backoff for up to 24 hours, reusing the same Idempotency-Key and eventId Given the receiver returns any 2xx status When a retry would otherwise occur Then no further retries are attempted and the delivery is marked delivered Given the receiver verifies the signature When it recalculates HMAC-SHA256 over the raw payload with the shared secret and timestamp tolerance <= 5 minutes Then the signature matches and the request is accepted Given webhook versioning When an event is sent Then the JSON payload includes specVersion == "1.0" and apiVersion == "v1"
List and Inspect Quota Policies with Pagination and Filters
Given a valid token with scope "quota.manage" When GET /v1/accounts/{A}/quota/policies?limit=20&cursor=abc&status=active Then respond 200 with JSON array length <= 20 and pagination.nextCursor present when more results exist; only policies with status == "active" are returned Given missing "quota.manage" scope When calling the endpoint Then respond 403 with error.code == "forbidden.scope" Given an account with > 100 policies When paginating until pagination.nextCursor is null Then all policies are retrievable exactly once without duplicates Given Accept: application/json When calling the endpoint Then Content-Type == "application/json; charset=utf-8" and each policy includes fields: id, name, limits.soft, limits.hard, window.type, window.duration, status
Global Rate Limiting and Quota Headers
Given a client making requests under a single access token When requests are within the assigned limit Then every API response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers with correct values Given the client exceeds the assigned rate limit When an additional request is made Then respond 429 Too Many Requests with Retry-After header and no side effects Given REST and GraphQL requests use the same access token When rate limiting is applied Then both share the same rate-limit bucket and headers reflect the shared state Given time passes beyond X-RateLimit-Reset When the client retries Then requests are accepted and headers reflect a refreshed remaining count
API Versioning, SDKs, and Documentation Availability
Given REST calls include Accept: application/vnd.timeglue+json; version=1 When hitting /v1 endpoints Then semantics conform to version 1 and responses include X-Timeglue-API-Version: v1 Given a request specifies an unsupported version When processed Then respond 400 with error.code == "unsupported_version" Given no version header is provided When calling /v1 endpoints Then the default version is v1 and behavior matches documented v1 semantics Given official SDKs for Node.js, Python, and Java When installed from their respective registries Then each exposes methods to retrieve quota metrics, list policies with pagination, and verify webhook signatures, with typed models and automated retries for 429/5xx Given the public documentation site When visited Then the OpenAPI 3.1 spec and GraphQL SDL are published, OAuth scope matrix is documented, webhook signature verification examples are provided for each SDK, and copy-paste examples execute successfully against a sandbox environment

Cap Bands

Apply conditional attendee caps by segment (internal vs. external), tier, region, or time of day. Automatically waitlist overflow, suggest smaller-group alternates, and enforce the right mix of attendees so sessions stay productive and compliant.

Requirements

Segment-based Cap Rules Engine
"As a remote team lead, I want to set different attendee limits by segment so that meetings stay productive and compliant without manual policing."
Description

Provide a configurable rules engine that lets organizers and admins define attendee caps by segment (internal vs. external), customer tier, region, role, and custom tags. Rules can be attached to meeting types and templates, with precedence and conflict resolution when multiple rules apply. At booking time, the engine evaluates invitee metadata from directory/CRM integrations to compute allowable seats per segment and total caps. The system surfaces inline validation, clear error messages, and guidance when a booking would exceed caps, and logs decisions for auditability. This integrates with Timeglue’s existing availability and template model so caps are enforced consistently across smart links and direct bookings.

Acceptance Criteria
Rule Precedence and Conflict Resolution
Given an admin defines multiple cap rules by segment, tier, region, role, and tags for a meeting type And each rule has an explicit priority value and labels its matching attributes When a booking context matches two or more rules with conflicting caps Then the engine selects the rule with the highest priority And if priorities tie, the rule matching the greatest number of attributes is selected And if still tied, the most recently modified rule is selected And the engine exposes the selected rule ID and effective caps (per-segment and total) to the UI and logs
Rule Attachment to Templates and Meeting Types
Given a template has cap rules A and B And a meeting type derived from that template adds rule C and overrides rule B When bookings are made using that meeting type Then rule C applies and rule B from the template is ignored, while rule A remains in force And removing the override restores the template’s rule B And publishing a meeting type without any rules inherits all template rules And rules can be reused across multiple meeting types without duplication and changes propagate according to reference/override semantics
Booking-Time Evaluation with Directory/CRM Metadata
Given invitees are added to a booking and their segment, tier, region, role, and tags are obtained from directory/CRM integrations When the host adds or removes invitees or the meeting time changes Then the engine recalculates per-segment and total utilization in real time And missing metadata is handled per configuration: assign to default segment or block with a specific error listing the affected invitees And if an integration is unavailable, the engine fails closed (blocks booking) and surfaces a retry option and incident code And the recalculation completes within 300 ms for up to 200 invitees
Inline Validation, Errors, and Alternate Suggestions
Given caps are defined for internal and external segments with totals When adding an invitee would exceed a segment or total cap Then the Book/Send action is disabled and an inline error displays the segment exceeded, allowed cap, current count, and overage And a “See alternates” action suggests smaller-group options (e.g., split into N sessions by region/role) and which invitees to remove to comply And resolving the overage (removing invitees or adjusting rules) immediately re-enables Book/Send
Overflow Waitlist and Required Mix Enforcement
Given a meeting type has per-segment caps and waitlisting enabled with segment priorities When additional invitees are added beyond caps Then excess invitees are placed on a waitlist in time-added order within segment priority And host and waitlisted invitees receive notifications with position and next steps And when seats open, invitees are auto-promoted in order and all parties are notified And if promotion would violate required mix constraints (e.g., min 2 internal), promotion is skipped and the next eligible invitee is considered
Decision Logging and Auditability
Given the engine evaluates caps during booking creation or update When a decision is made (allow, waitlist, block) Then an immutable audit record is written with timestamp, booking ID, actor, source (smart link/direct/API), rules evaluated, selected rule(s), computed caps, counts by segment, and rationale And admins can filter and export these logs by date range, meeting type, rule ID, and outcome And audit retrieval returns within 5 seconds for queries over the past 30 days
Consistent Enforcement Across Smart Links and Direct Bookings
Given identical rules and the same invitee set When a booking is attempted via a smart link and via direct booking in the app Then the engine produces the same allow/block/waitlist outcomes and messages in both flows And any post-booking attendee edits trigger the same validations and outcomes before changes are saved
Time-window Cap Bands on Timeline
"As an organizer, I want caps that change by time of day so that I can protect focus hours while allowing larger groups in designated windows."
Description

Enable organizers to paint cap bands directly on the draggable scheduling timeline, varying caps by time of day and day of week. Bands can lower caps during focus hours or raise them during open office windows, and can be repeated via recurrence rules. The UI layers cap bands alongside work hours, holidays, and focus blocks, with visual cues for where caps differ. The booking engine interprets these bands in real time, ensuring the correct cap for the specific time slot is applied. Bands are saved within meeting type templates for reuse across smart links.

Acceptance Criteria
Paint Variable Cap Bands by Time and Day
Given an organizer has opened the timeline editor for a meeting type template When they drag from 09:00 to 12:00 on Monday and set cap to 6 Then a cap band from 09:00–12:00 Monday is created with cap 6 and a visible label "6" And when they multi-select Tuesday–Thursday and apply cap 6 for 09:00–12:00 Then bands for Tuesday–Thursday are created with cap 6 And painting over an existing band replaces the cap within the overlapped interval with the new cap And all band edges snap to 5-minute increments
Recurring Cap Bands
Given a cap band is created for 14:00–17:00 on Wednesday with recurrence rule "Weekly" When the template is saved Then the band recurs on each Wednesday going forward within the template's scheduling horizon And when the recurrence rule is edited to "Weekdays" Then the band appears on Monday–Friday at 14:00–17:00 And deleting the band removes all its recurrences
Layered UI with Visual Cues
Given work hours, holidays, and focus blocks are defined for the template And a cap band exists that overlaps these intervals When the timeline is displayed Then cap bands render as a distinct layer with a unique color/pattern and label And no layer occludes another; intersections are indicated with a clear visual cue/legend And intervals where the cap differs from the baseline cap are visually highlighted And all visual elements meet WCAG AA contrast (>= 4.5:1 for text)
Booking Engine Applies Correct Cap Per Slot
Given a template baseline cap of 10 and a Monday 09:00–12:00 cap band of 6 When a booking request is made for Monday 10:30–11:00 Then the effective cap for that slot is 6 And when a booking request is made for Monday 12:00–12:30 Then the effective cap for that slot is 10 And if concurrent accepted attendees would exceed the effective cap for a slot, additional requests are waitlisted
Time Zone and DST Consistency
Given the template's canonical timezone is America/Los_Angeles And a cap band exists for 09:00–12:00 Monday–Friday When the editor is viewed in Europe/Berlin Then the timeline displays the band at 18:00–21:00 local time but stores and applies it relative to America/Los_Angeles And across DST changes, the band continues to apply to the 09:00–12:00 America/Los_Angeles wall-clock interval for those days And bookings from any timezone use the effective cap derived from the canonical timezone
Template Persistence and Smart Link Reuse
Given a template with cap bands is saved When a smart link is created from that template Then bookings made via the smart link enforce the template's cap bands And duplicating the template preserves cap bands And removing a cap band from the template updates all subsequently generated smart links
Automatic Waitlist and Promotion
"As an external invitee, I want to be automatically waitlisted and promoted when a spot opens so that I don’t have to coordinate manually."
Description

When a session reaches its configured cap, additional registrants are placed on an ordered waitlist with policies for ordering (FIFO, segment or tier-based priority). On cancellations or capacity changes, the system automatically promotes waitlisted attendees, sends confirmations via email/Slack, and updates calendar invites. Organizers can override, re-order, or lock the waitlist, with full audit trails. The waitlist state is exposed in the booking UI and via API so stakeholders always see current capacity and queue position.

Acceptance Criteria
FIFO waitlist when session reaches cap
Given a session with capacity 10 and FIFO waitlist policy enabled And 10 attendees have confirmed registrations When an 11th attendee attempts to register Then the attendee is placed on the waitlist with position 1 And their waitlist timestamp is recorded with at least millisecond precision And they receive a waitlist confirmation message in the booking UI And no calendar invite is issued to the waitlisted attendee And the session’s displayed remaining capacity is 0 and waitlist count is 1 to all viewers
Priority-based waitlist ordering by segment and tier
Given a session with capacity 2 and a waitlist policy of Segment then Tier priority (Internal > External; Tier Gold > Silver) And four attendees register in order: External-Silver (A), Internal-Silver (B), External-Gold (C), Internal-Gold (D) When A and B are confirmed and C and D are added to the waitlist Then the waitlist order is D (Internal-Gold) position 1, C (External-Gold) position 2 And within the same priority band, earlier submission time sorts ahead of later And switching the policy to FIFO reorders the waitlist strictly by submission time And the reorder action creates an audit entry recording actor, timestamp, old order, and new order
Automatic promotion on cancellation or capacity increase
Given a session with capacity 2, two confirmed attendees, and a waitlist with positions 1..n And notification channels email and Slack are configured When a confirmed attendee cancels their registration Then the attendee at waitlist position 1 is promoted within 60 seconds And they receive a confirmation email and Slack DM containing session details and join information And a calendar invite is created and sent to the promoted attendee reflecting the correct time zone And the waitlist positions decrement accordingly and the promoted attendee is removed from the waitlist And the organizer is notified of the promotion via Slack channel notification And when the organizer increases capacity by 1, the top waitlisted attendee is promoted within 60 seconds with the same notifications and invite updates
Organizer override, reorder, and lock with audit trail
Given an organizer with manage permissions views the waitlist When the organizer manually reorders the waitlist (drag-and-drop or set rank) Then the new order is immediately persisted and visible to all stakeholders And an immutable audit record is written containing actor, timestamp, reason, previous order, and new order And when the organizer locks the waitlist Then automatic promotions are suspended and queued while locked And any capacity openings during lock do not issue confirmations or calendar invites And upon unlocking, queued promotions are processed in the locked-in order with full notification and calendar updates, and an audit entry is recorded
Booking UI shows capacity and queue position
Given a prospective attendee opens the booking page for a session at capacity When they select a time and submit their details Then the UI displays their waitlist position number and an estimated promotion window And the session card shows current capacity (0 remaining) and total waitlist count And the UI updates the waitlist position in real time if it changes while the page remains open And accessibility meets WCAG 2.1 AA for all waitlist indicators and live announcements
API exposes waitlist state and positions
Given an authenticated client calls GET /v1/sessions/{id}/waitlist with default parameters Then the response status is 200 and includes: sessionId, capacity, lockState, policy, totalWaitlisted, and an ordered array of entries with attendeeId, position, segment, tier, submittedAt And results are paginated with cursor-based pagination (limit default 50, max 200) and include nextCursor when more results exist And ETag and Last-Modified headers are returned and respected on subsequent conditional requests And positions and totalWaitlisted reflect the current state within 5 seconds of any promotion, cancellation, reorder, or capacity change And POST /v1/sessions/{id}/waitlist/reorder with a valid order is idempotent and returns 200 with the new order and an auditId
Concurrency safety during simultaneous events
Given two cancellations and three new registrations occur within a 2-second window on a session with a waitlist When the system processes these events Then no more than the available number of seats are promoted And each promotion is executed exactly once using idempotency keys, with no duplicate notifications or calendar invites And the final confirmed count equals capacity, and remaining registrants are placed on the waitlist in policy-compliant order And all resulting state transitions are recorded in the audit log and exposed via API within 5 seconds
Mix Enforcement and Validation Rules
"As a program manager, I want to enforce the right internal/external mix so that sessions meet policy and quality standards."
Description

Allow admins to define mix constraints such as minimum internal facilitators, maximum externals without corresponding internals, regional representation requirements, or ratios by tier. The system validates these constraints during booking and updates, offering hard blocks or soft warnings with justifications. Real-time checks run against attendee lists, resources, and role assignments, and continue to enforce constraints when attendees are added or removed post-booking. Violations generate alerts and are captured in compliance logs for reporting.

Acceptance Criteria
Hard Block: Minimum Internal Facilitators Enforced During Booking
Given an active rule "Min Internal Facilitators = 2" for meeting type "Strategy Sync" And the selected attendees include 1 Internal Facilitator When the organizer attempts to book the meeting Then the booking is hard-blocked and not created And the UI/API returns error code MIX_RULE_VIOLATION with HTTP 409 And the error message includes rule_name, required_count=2, actual_count=1 And validation latency is <= 500 ms at p95 And a compliance log record is written with outcome=blocked, rule_id, booking_id (temp or UUID), actor_id, required_count, actual_count, timestamp
Soft Warning: External-to-Internal Ratio Requires Justification
Given an active rule "External-to-Internal Ratio <= 1:1" with severity=warning And the attendee selection results in 3 Externals and 2 Internals When the organizer attempts to book the meeting Then the system displays a soft warning with code MIX_RULE_WARNING And the warning message includes rule_name, required_ratio=1:1, actual_ratio=3:2 And the booking can proceed only after a justification text of at least 10 characters is provided And the justification is stored with the booking and in the compliance log And validation latency is <= 500 ms at p95
Hard Block: Regional Representation Requirement Across Time Zones
Given an active rule "At least 1 attendee from APAC and 1 from EMEA" for meeting type "Quarterly Review Board" with severity=hard And the attendee list includes 0 from APAC and 2 from EMEA When the organizer attempts to book the meeting Then the booking is rejected with MIX_RULE_VIOLATION and HTTP 409 And the error message enumerates missing_regions=["APAC"] And no calendar event is created or modified And a compliance log record is written with outcome=blocked, rule_id, missing_regions, actor_id, timestamp
Real-Time Enforcement on Post-Booking Attendee Edits
Given a confirmed booking that currently satisfies all mix rules When an editor removes the only Internal Facilitator Then the change is prevented for hard rules with MIX_RULE_VIOLATION (HTTP 409 for API, inline error in UI) And for warning-severity rules, the change requires a justification >= 10 characters before saving And revalidation occurs on every add/remove via UI, API, and calendar sync hooks And an in-app alert is sent to the organizer within 5 seconds for any attempted or resulting violation And a compliance log record is appended capturing old_state, proposed_state, outcome, actor_id, timestamp
Role and Resource Assignment Validation (Licensed Internal Facilitator Required)
Given an active rule ">= 1 Facilitator must be Internal and Licensed=true" for session type "Client Workshop" And role assignments currently include 0 Internal Licensed Facilitators When the organizer attempts to save role changes or book the meeting Then the operation is blocked (hard) with MIX_RULE_VIOLATION and HTTP 409 for API calls And the message details required_role="Facilitator", required_internal=true, required_licensed=true, actual_count=0 And suggestions section lists current candidates count (>=0) by filter criteria And a compliance log entry captures rule_id, outcome, actor_id, counts, timestamp
API Contract: Standardized Validation Outcomes and Codes
Given a createBooking or updateBooking API request When validation runs Then if no violations: respond 201 (create) or 200 (update) with violations=[] And if only warnings: respond 202 with violations=[{severity:"warning", rule_id, details}] and require justification field; persist on success And if hard violations: respond 409 with violations=[{severity:"hard", rule_id, details}] and no changes persisted And all responses include validation_id, rule_evaluated_count, p95_latency_ms And each validation writes a compliance log record with outcome in {pass, warning, blocked}
Concurrency: Atomic Validation Prevents Race Condition Violations
Given two editors concurrently modify the attendee list in ways that would jointly violate the External-to-Internal 1:1 ratio When both submissions are processed Then the system applies atomic validation ensuring the final saved state satisfies all rules And at most one operation succeeds; the other fails with HTTP 409 and code VERSION_CONFLICT or MIX_RULE_VIOLATION including latest_attendee_counts And no partial changes are persisted from the failed operation And both attempts are captured in compliance logs with correlation_ids and timestamps
Smaller-group Alternate Suggestions
"As an organizer, I want smaller-group alternates suggested when a session is full so that I can maintain momentum without manual rescheduling."
Description

When caps are exceeded or mix rules cannot be satisfied, suggest alternate times that meet capacity and policy constraints, or propose auto-splitting into multiple smaller sessions optimized around attendees’ overlap windows. Provide ranked suggestions with predicted attendance fit, one-click creation of alternates, and automatic distribution of waitlisted or overflow attendees. Suggestions respect work hours, holidays, focus blocks, and existing cap bands, and preserve organizer preferences such as required attendees or presenters.

Acceptance Criteria
Suggestions Trigger on Cap/Mix Violation
Given a meeting draft with defined cap bands and attendee mix rules And the selected attendees cause either a cap to be exceeded or a mix rule to be unsatisfied When the organizer reviews the scheduling panel Then the system automatically displays the Suggestions module And the module includes at least 3 and at most 10 suggestion items when such items exist And the module is rendered within 2 seconds from panel open on a dataset of up to 50 attendees and 6 time zones And if no caps/mix violations exist, the Suggestions module is not auto-opened
Alternate Times Meet Policy Constraints
Given a set of generated alternate time suggestions When inspecting any suggestion Then all included attendees are within their configured work hours for that datetime And no included attendee is scheduled on their configured holidays And no included attendee's non-overridable focus blocks are violated And all relevant cap bands (segment, tier, region, time-of-day) are satisfied with utilization <= 100% And the suggestion does not overlap any existing event marked as busy for required attendees and presenters
Auto-Split Into Smaller Sessions
Given caps or mix rules prevent a single-session solution When generating split-session proposals Then the system proposes between 2 and 4 smaller sessions optimized for highest overlapping availability And each proposed session independently satisfies all cap bands and mix rules And required attendees and presenters are included in every split session unless explicitly configured as optional And no assigned attendee is double-booked across the proposed sessions And each split session includes a predicted attendance fit score
One-Click Alternate Creation
Given a selected single-session suggestion or split-session proposal When the organizer clicks Create Alternates Then the system creates the corresponding event(s) with inherited title, description, location, privacy, cap bands, required attendees, presenters, and organizer preferences And invites are sent to the assigned attendees only And event creation completes within 3 seconds and returns success confirmation And created alternates are linked to the original request for tracking
Automatic Distribution of Overflow/Waitlist
Given a pool of waitlisted or overflow attendees And one or more alternates have been created from suggestions When auto-distribution runs Then attendees are assigned to alternates in descending suggestion rank while respecting caps, mix rules, and attendee availability And no attendee is assigned to overlapping alternates And required attendees and presenters are not auto-redistributed away from their designated session(s) And notifications are sent to reassigned attendees with the correct alternate details And an audit log records each move with timestamp and reason
Ranked Suggestions With Fit Score and Rationale
Given suggestions have been generated When viewing the suggestions list Then items are sorted by a numeric Predicted Attendance Fit score (0–100) descending And each item shows the fit score, count of can-attend attendees, cap utilization percentage, and a short rationale And sorting is stable and deterministic for identical scores using tie-breakers: higher required-attendee availability, then earlier datetime
No Viable Suggestions and Performance Guarantees
Given the input constraints admit no valid times or splits When suggestion generation completes Then the system displays "No viable suggestions" with at least one reason code (e.g., required attendee unavailable, cap band too restrictive) And no alternates are created automatically And generation completes within 5 seconds for up to 50 attendees, 6 time zones, 2 cap bands, and 200 calendar conflicts scanned per attendee And a retry option is offered after constraint edits
Policy Templates and Admin API
"As a workspace admin, I want reusable cap policies and an API so that standards are consistent and easy to manage at scale."
Description

Introduce reusable cap policy templates that encapsulate segment caps, mix rules, and time-window bands. Templates can be versioned, approved, and assigned by workspace, team, or meeting type, with role-based access controls. Provide API endpoints and webhooks to read, validate, and apply policies, plus SCIM/HRIS and CRM mappings to keep segment, tier, and region attributes current. All changes are tracked with audits and effective dates to ensure consistent governance across Timeglue.

Acceptance Criteria
Create and Validate Cap Policy Template
Given an Org Admin with scope policy:write When they POST /admin/policies with a template defining segment caps, mix rules, and time-window bands Then the API validates against the policy schema and responds 201 with template_id, version "1.0.0", and status "Draft" And invalid payloads respond 422 with field-level error codes and messages And the created template is retrievable via GET /admin/policies/{id} and matches the submitted rules exactly
Versioning and Approval Workflow
Given a Draft template version 1.0.0 When a new version is created via POST /admin/policies/{id}/versions from 1.0.0 Then the new version is 1.1.0 in status "Draft" and 1.0.0 remains immutable And status transitions follow Draft -> InReview -> Approved or Rejected; any other transition is rejected with 409 And approvals require a user with policy:approve who is not the author; approval records approver_id, timestamp, and comment And Approved versions are read-only; attempts to modify return 409
Assignment and Effective Dating Resolution
Given an Approved template version When it is assigned via POST /admin/policies/{id}/assignments to a workspace/team/meeting_type with effective_start Then only Approved templates can be assigned; non-approved return 409 And specificity precedence is meeting_type > team > workspace for conflict resolution And GET /admin/policies:resolve?workspace_id=...&team_id=...&meeting_type=...&at=timestamp returns the resolved template_id and version effective at the provided timestamp And invites created before effective_start are not re-evaluated; after effective_start new scheduling uses the assigned policy And assignments can be ended with effective_end; assignments cannot be backdated earlier than now and must not overlap for the same target and policy
RBAC and API Scope Enforcement
Given user roles Org Admin, Team Admin, and Viewer and OAuth scopes policy:read, policy:write, policy:approve When a request is made to create, approve, assign, or delete policies Then required roles and scopes must be present; missing authentication returns 401; insufficient permissions return 403 with code "insufficient_scope" And UI actions for unauthorized operations are hidden or disabled; API tokens without scopes cannot perform those operations And all authorized operations are recorded with actor_id, role, scopes, ip, and user_agent in the audit log
Policy Application During Scheduling
Given a resolved policy for a scheduling context and a set of attendees with segment, tier, and region attributes When a meeting is created via POST /meetings with proposed start/end and attendees Then the policy is applied atomically: segment caps, required mix rules, and time-window bands must all pass And violations return 409 with code POLICYCAP_EXCEEDED or POLICYMIX_VIOLATION and include waitlist_token and up to 3 alternate smaller-group suggestions that satisfy the policy And on success the meeting is created (201) and a policy.applied event is emitted containing policy_id, version, and evaluation_id And idempotency via Idempotency-Key header guarantees the same outcome for retries within 24 hours And a dry-run via POST /admin/policies:validate with the same inputs returns pass true/false and findings without side effects
Webhooks Delivery and Security
Given a subscribed webhook endpoint When policy.approved, policy.assigned, policy.applied, or policy.violation events occur Then events are delivered within 5 seconds of commit with headers X-Timeglue-Event-Id, X-Timeglue-Event-Type, X-Timeglue-Timestamp, and X-Timeglue-Signature (HMAC-SHA256) And 2xx responses acknowledge delivery; non-2xx are retried with exponential backoff for up to 24 hours with at-least-once semantics; deduplication via X-Timeglue-Event-Id And signature verification using the workspace secret succeeds for valid requests; invalid signatures are rejected with 401
SCIM/HRIS and CRM Attribute Sync and Mapping
Given active connectors for SCIM/HRIS and CRM with mappings for segment, tier, and region When inbound changes are received or fetched Then incremental sync runs at least every 15 minutes and a full reconciliation runs daily at 02:00 UTC And attribute precedence is SCIM > HRIS > CRM unless an explicit override policy is configured; conflicts are logged with source and resolution And updates to user attributes trigger immediate re-evaluation of affected upcoming meetings; resulting violations generate policy.violation events And source outages do not block scheduling; last-known attributes are used for up to 72 hours and an alert is emitted to Org Admins

Audit Ledger

Generate a tamper-evident, exportable log for every gate decision—who booked, why they were allowed/blocked, which rule version applied, SSO attributes used, quota state, and approver. One-click CSV/JSON exports satisfy audits and build trust with stakeholders.

Requirements

Append-only Hash-Chained Ledger
"As a security auditor, I want an append-only, tamper-evident log of gate decisions so that I can prove the integrity of scheduling controls during audits."
Description

Implement an append-only event store for all scheduling gate decisions that forms a tamper-evident chain via cryptographic hashes. Each entry must reference the previous entry’s hash and include its own canonicalized payload hash to prevent undetected modification or reordering. Writes are triggered synchronously by the booking/gating pipeline and include retries and idempotency keys to ensure exactly-once recording without impacting booking latency. The ledger partitions by workspace to scale horizontally and supports time-ordered pagination with stable identifiers. Storage enforces logical immutability (WORM semantics) at the application layer and validates chain continuity on write. Operational tooling exposes chain health metrics and alerts on gaps, forks, or integrity check failures.

Acceptance Criteria
Synchronous Exactly-Once Write with Idempotency
Given a booking/gating decision D with idempotency_key K for workspace W When the pipeline issues 1..N duplicate write attempts for K due to retries within 2 seconds Then the ledger for W contains exactly one entry with idempotency_key K and a stable entry_id And duplicate attempts return 200 with the same entry_id and do not append a new entry And ledger write overhead adds <= 20 ms p95 and <= 80 ms p99 to the booking path under 500 writes/sec/partition with 32 partitions
Append-Only WORM Enforcement
Given an existing ledger entry E When any update (PUT/PATCH) or delete is attempted via public or internal storage APIs Then the operation is rejected with HTTP 409 and error_code LEDGER_IMMUTABLE And no mutation occurs (ETag/version unchanged) and the chain head is unchanged And the attempt is logged with actor, workspace, entry_id, and timestamp
Hash-Chain Continuity Validation on Write
Given the current head hash Hn for workspace W When a new entry En+1 is written Then En+1.prev_hash equals Hn And En+1.entry_hash equals SHA-256(prev_hash || payload_hash) encoded as lowercase hex And if prev_hash != Hn at commit time, the write fails atomically with HTTP 409 and error_code LEDGER_CHAIN_BROKEN and no partial record is stored
Deterministic Canonical Payload Hashing
Given two logically identical payloads that differ only by JSON whitespace, field order, or insignificant formatting When computing payload_hash Then both payloads yield the same payload_hash And payload_hash is the 64-character lowercase hex of SHA-256 over the canonicalized UTF-8 payload And any semantic field change produces a different payload_hash
Workspace Partitioning and Time-Ordered Pagination
Given entries exist for workspaces W1 and W2 and W1 has N >= 200 entries When listing W1 with page_size P and a stable next_cursor Then results contain only W1 entries in ascending event_time then entry_id order And repeating the same cursor returns the same page even under concurrent writes And no entry is duplicated across adjacent pages and none are skipped And each entry_id is unique and stable within W1
Concurrent Write Serialization and Fork Prevention
Given 50 concurrent write attempts for workspace W within a 10 ms window, initially referencing the same observed head Hn When all attempts complete Then the chain length increases by 50 with a single linear history (no forks) And at most one attempt initially commits; others detect head movement and retry up to 2 times before succeeding And each committed entry's prev_hash equals the immediate predecessor's entry_hash (no gaps)
Chain Health Metrics and Alerting
Given a background integrity checker runs every 5 minutes When it detects a gap, fork, or hash mismatch in any workspace Then metric ledger_chain_integrity{workspace,reason} is set to fail for that workspace within 1 minute And alert LedgerChainIntegrityFailure fires within 2 minutes including workspace, entry_id (if applicable), and reason And when the issue is resolved, the metric returns to ok and the alert auto-resolves
Comprehensive Decision Event Schema
"As a compliance officer, I want each decision to capture who, why, which rules, and what attributes were used so that I can reconstruct any booking outcome end-to-end."
Description

Define and implement a versioned event schema that captures who initiated the booking, the target, workspace/team, link or flow ID, decision outcome (allowed/blocked/pending), and a structured rationale. Record the ruleset and rule version applied, evaluation trace identifiers, and any SSO attributes or policy claims used at decision time with field-level sensitivity classifications. Include quota state snapshots (before/after counters and window identifiers), approver identity and channel (if applicable), timestamps, correlation/request IDs, and external calendar IDs. Sensitive attributes are encrypted at rest and redacted in views based on caller permissions. Provide forward- and backward-compatible schema evolution with migrations and a dictionary of fields/semantics. Validate events at ingestion to ensure completeness and consistency across all decision sources.

Acceptance Criteria
Allowed Decision Event Captures Required Fields
Given a user with SSO attributes books via a smart link within working hours and no quota is exceeded When the decision outcome is allowed and a calendar reservation is created Then an event is ingested with a non-empty schemaVersion and source="decision-engine" And the event includes initiatorId, targetId, workspaceId, teamId (nullable), linkId or flowId (exactly one), outcome="allowed", rationale.code and rationale.details structured And the event includes rulesetId, ruleVersion, evaluationTraceId, correlationId, requestId, externalCalendarId And the event includes quota.before.count, quota.after.count, and quota.windowId with after.count = before.count + 1 And approver.id and approver.channel are null And createdAt and decisionAt timestamps are present in ISO 8601 with millisecond precision and decisionAt >= createdAt And the event passes ingestion validation and is persisted
Blocked Decision Event Records Structured Rationale and Rule Version
Given a user attempts to book outside acceptable hours or violates a policy When the decision outcome is blocked Then an event is ingested with outcome="blocked" and a non-empty rationale.code, rationale.category, and rationale.message And rulesetId and ruleVersion identify the exact rule that produced the block And evaluationTraceId links to the rule evaluation trace And quota.before.count, quota.after.count, and quota.windowId are present with after.count = before.count And approver.* fields are null And required identifiers (initiatorId, targetId, workspaceId, linkId or flowId, correlationId, requestId) are present and non-empty And the event passes ingestion validation and is persisted
Pending Decision Event With Approval Metadata
Given a booking requires manager approval via Slack When the decision outcome is pending Then an event is ingested with outcome="pending" and approval.requestId, approver.channel="slack", and approver.required=true And approver.id is null until approval is granted And a subsequent allowed/blocked event references approval.requestId and sets approver.id to the approving user's ID And both events share the same correlationId and have consistent initiatorId, targetId, and linkId/flowId And ingestion validation enforces that approval.requestId is present for outcome="pending"
Field-Level Sensitivity, Encryption, and Redaction
Given SSO attributes (e.g., department, role, costCenter) are included in the event When sensitive fields are stored Then each sensitive field has sensitivityClassification in ["public","internal","sensitive","restricted"] And values for fields classified as "sensitive" or "restricted" are encrypted at rest using the configured KMS key and are unreadable without decryption And API/read views for callers without permission return "[REDACTED]" for those fields And API/read views for authorized callers return decrypted values And access is logged with callerId, decisionEventId, fieldsAccessed, and timestamp
Schema Versioning and Migration Compatibility
Given schemaVersion N is deployed and events exist for N-1 and N When a new optional field is added in version N+1 Then consumers that only understand N and N-1 can parse N+1 events without error And producers can continue emitting N events until migration cutover without data loss And the field dictionary is published with entries: fieldName, type, semantics, sensitivityClassification, versionIntroduced, versionDeprecated (nullable) And automated compatibility tests validate forward and backward compatibility across N-1, N, and N+1 And a migration script backfills the new field with a default or null where applicable
Ingestion Validation and Cross-Source Consistency
Given decision events originate from booking links, API bookings, and admin overrides When events are ingested Then required fields (initiatorId, targetId, workspaceId, outcome, rulesetId, ruleVersion, evaluationTraceId, correlationId, requestId, createdAt, decisionAt) are present and correctly typed And linkId xor flowId is present (exactly one) And timestamps are ISO 8601 with millisecond precision and decisionAt - createdAt is between 0 and 60 seconds And quota.after.count - quota.before.count ∈ {0,1} depending on outcome And events failing validation are rejected to a dead-letter queue with a validationErrorCode and are not persisted And validation metrics (rate, failure count by code, source) are emitted
One-Click CSV/JSON Exports
"As an operations manager, I want one-click CSV/JSON exports with filters so that I can deliver audit evidence without manual data wrangling."
Description

Add UI and API endpoints to generate audited exports of ledger entries with one click, supporting CSV and JSON formats. Exports allow filtering by date range, workspace/team, decision outcome, link ID, user, and rule version, and let users select columns to minimize PII exposure. Large exports run asynchronously as jobs, provide progress status, and deliver expiring, access-scoped download URLs with optional compression. Each export includes a manifest containing record counts, file checksums, and the ledger chain tip hash at export time. Export processes respect retention, redaction, and access policies and are rate-limited to protect system stability. Notifications inform requesters upon completion, and errors include actionable remediation hints.

Acceptance Criteria
UI One-Click Export with Filters and Column Selection
Given I am a workspace admin or auditor with export permission When I open Audit Ledger > Exports and choose CSV or JSON And I set filters for date range, workspace/team, decision outcome, link ID, user, and rule version And I select columns to include Then the export contains only records matching all filters And only the selected columns are present in the output And the export excludes any columns not selected to minimize PII And CSV output includes a header row and escapes fields per RFC 4180 (UTF-8); JSON output contains one object per ledger record with keys matching selected columns And timestamps are exported in ISO 8601 UTC
API Export Endpoint with Filter Parity and Response Semantics
Given I have a valid API token scoped to the target workspace When I POST to /v1/ledger/exports with format, filters (date range, workspace/team, decision outcome, link ID, user, rule version), and column selections Then the API validates inputs and returns 400 with field-level errors for invalid filters or columns And the API applies identical filtering and column-selection behavior as the UI And if the estimated result size is below the asynchronous threshold T, it returns 200 with the file stream; otherwise it returns 202 with job_id and status_url
Asynchronous Job Handling with Progress and Notifications
Given an export exceeds threshold T or is forced async When the export job is created Then its status endpoint returns one of: queued, running, completed, failed, canceled And progress includes percent_complete, processed_records, and (if known) estimated_total And upon completion the requester receives an in-app notification and, if enabled, an email with the download link And failed jobs include error_code and human-readable message
Expiring, Access-Scoped Download URLs and Optional Compression
Given an export job completes When a download URL is generated with optional compression (none, zip, gzip) Then the URL is signed and scoped to the requester’s workspace and permissions And the URL expires after the configured TTL and returns 403 after expiry And compressed downloads contain both the data file(s) and manifest; uncompressed exports include both as separate files And Content-Type and file extensions match the selected format and compression
Export Manifest with Counts, Checksums, and Chain Tip Hash
Given any export completes When the manifest is generated Then it includes format, applied filters, selected columns, generated_at (UTC), exporter_version, and ledger_chain_tip_hash And it includes record_count and per-file SHA-256 checksums And verifying the checksums against the downloaded files succeeds And the manifest file is delivered alongside the data
Policy Enforcement: Retention, Redaction, Access Control
Given workspace retention and redaction policies are configured When an export is requested Then records outside the retention window are omitted And fields marked for redaction are consistently redacted to match in-app views And only users with Export Ledger permission can request exports for their scoped teams/workspaces And an audit event is recorded for export creation, completion, download, and failure with requester identity
Rate Limiting and Actionable Error Responses
Given per-user and per-workspace export rate limits are configured When limits are exceeded Then the API/UI returns 429 with Retry-After and next_available_time And error responses include actionable remediation hints (e.g., reduce date range, remove columns, switch to async) And transient service issues return 503 with retryable=true; validation errors return 400 with field-level details; permission issues return 403
Role-Based Ledger Access Controls
"As a workspace admin, I want granular permissions and masking for ledger access so that auditors can review data without exposing unnecessary PII."
Description

Introduce granular RBAC and ABAC for viewing, searching, and exporting the audit ledger, with permissions scoped by workspace, team, and time window. Distinct capabilities include View Summary, View Sensitive Fields, Export, and Manage Ledger Settings, each assignable to roles such as Admin, Owner, and Auditor. Integrate with SSO (SAML/OIDC) and optional SCIM to map identity attributes to permissions and apply field-level masking for sensitive SSO attributes. All access attempts and export actions are themselves logged to a separate administrative audit stream. Provide administrative UI and APIs to grant, review, and revoke access with reason tracking and expiry. Deny-by-default posture ensures least-privilege and reduces compliance risk.

Acceptance Criteria
Deny-by-Default and View Summary Enforcement
Given a user without any ledger permissions When they request any ledger UI page or API endpoint Then the system returns HTTP 403 with error code LEDGER_ACCESS_DENIED and no ledger data is returned Given a user with the View Summary capability scoped to Workspace A When they list ledger entries via UI or API Then only non-sensitive summary fields (e.g., timestamp, actor, decision, rule_version, resource_id) are returned and entries are limited to Workspace A Given a user with only the View Summary capability When they retrieve a specific ledger entry by ID outside their scope Then the API returns HTTP 404 NOT_FOUND to prevent information disclosure
Field-Level Masking and View Sensitive Fields
Given a user with only the View Summary capability When they view ledger entries containing sensitive SSO attributes Then those sensitive fields are redacted in UI as ••• and in API responses as "REDACTED" Given a user with the View Sensitive Fields capability within their allowed scope When they view the same ledger entries Then sensitive fields are displayed in clear text in both UI and API responses Given a user with the View Sensitive Fields capability When they request entries that are outside their allowed scope Then those entries are not returned by the API Given a user without the View Sensitive Fields capability When they export data (if allowed to export) Then sensitive fields in the exported file are redacted as "REDACTED"
ABAC Scope by Workspace, Team, and Time Window
Given a policy granting User U access to Workspace A, Team T1, and time window 2025-06-01T00:00:00Z to 2025-06-30T23:59:59Z When User U lists ledger entries without filters Then only entries that match Workspace A AND Team T1 AND timestamps within the time window are returned Given User U explicitly queries a date range that exceeds their allowed window When the request is processed Then only in-scope entries are returned and no out-of-scope entries are included Given User U requests a specific ledger entry by ID that does not fall within their scope When the API is called Then the API responds with HTTP 404 NOT_FOUND Given User U attempts to export with a date range exceeding their scope When the export API is called Then the API returns HTTP 403 LEDGER_EXPORT_SCOPE_VIOLATION and no file is generated
Export Controls and Export Event Auditing
Given a user with the Export capability scoped to Workspace A and an allowed time window When they export the ledger as CSV or JSON Then the file contains only in-scope entries and columns are masked according to the user's View Sensitive Fields capability Given a successful export When the file is generated Then the metadata includes total_records and generated_at (UTC), and the row count equals total_records Given a user without the Export capability When they attempt to export via UI or API Then the system returns HTTP 403 LEDGER_EXPORT_FORBIDDEN and no file is produced Given any export attempt (success or failure) When the request completes Then an admin audit event is recorded with actor_id, timestamp, scope filters, format (CSV/JSON), record_count (0 on failure), decision (allow/deny), and permission_version, and the event is queryable within 10 seconds
Administrative Audit Stream for Access Attempts
Given any attempt to access ledger-related UI or API (list, detail, export, settings) When the system processes the request Then an admin audit event is written with actor_id/service_id, action, resource, decision (allow/deny), reason_code, workspace_id, team_id, timestamp (UTC), client_ip, auth_method, and correlation_id Given 50 allowed and 50 denied ledger access attempts executed sequentially When querying the admin audit stream Then 100 corresponding events are present within 60 seconds of the last attempt with no missing events Given a request to delete or modify an admin audit event via public API When the request is executed Then the system returns HTTP 403 ADMIN_AUDIT_IMMUTABLE and the event remains unchanged
SSO/OIDC and SCIM Attribute Mapping to Capabilities
Given a user logs in with a SAML/OIDC assertion containing group "Auditors" mapped to the View Summary capability When the session is established Then the user can view summaries and cannot export or view sensitive fields Given the same user is added via SCIM to group "Ledger Exporters" mapped to the Export capability When the SCIM update is processed Then within 10 minutes or upon next login/token refresh (whichever occurs first) the user gains Export capability for their mapped scopes Given the user is deprovisioned or disabled in the IdP When they attempt to access any ledger endpoint Then within 5 minutes their session is invalidated and the API returns HTTP 401/403, and an admin audit event is recorded with reason_code IDP_DEPROVISIONED Given a login assertion missing required attributes or with ambiguous mappings When access is evaluated Then access is denied by default and an admin audit event is recorded with reason_code RBAC_DENY_BY_DEFAULT
Admin UI/API for Grant, Review, Revoke, and Manage Ledger Settings
Given an Admin with the Manage Ledger Settings capability When they grant any ledger capability to a principal via UI or API Then a reason is required and an optional expiry datetime can be set, the change takes effect within 1 minute, and an admin audit event records grantor, grantee, capability, scope, reason, expiry Given a grant with an expiry When the expiry time is reached Then the grant is automatically revoked, subsequent access attempts return HTTP 403 with reason_code GRANT_EXPIRED, and the revocation is logged Given an Admin attempts to grant a capability they do not possess or outside their own scope When the request is submitted Then the system blocks the change with HTTP 403 RBAC_CANNOT_ESCALATE and logs the attempt Given a reviewer lists active grants When filtering by user, team, workspace, or capability via UI or API Then the correct grants are returned with pagination and each item includes remaining_time_to_expiry Given the Admin updates masking policies or export limits under Manage Ledger Settings When the change is saved Then the change is versioned with change_id and change_summary, applied to subsequent requests within 1 minute, previous versions remain viewable, and the change is logged to the admin audit stream
Integrity Verification and Signed Exports
"As a risk manager, I want cryptographically signed exports and verification tools so that stakeholders can independently confirm nothing was altered."
Description

Provide end-to-end integrity verification by offering cryptographically signed exports and chain verification tools. Each export bundle is signed with a managed platform key and includes a public key reference and checksums to enable independent verification by stakeholders. Expose an API/CLI that recomputes the hash chain over a selected range and reports anomalies, with a UI indicator showing the latest verified chain tip. Periodically anchor the chain tip to an external timestamping service to make tampering externally detectable. On verification failures, block new exports and surface alerts to administrators. Documentation and samples enable auditors to validate integrity without Timeglue console access.

Acceptance Criteria
Signed Export Bundle Creation and Verification
Given an admin initiates an audit export for a specified ledger range When the export completes Then the bundle contains data files, a manifest with per-file checksums, a chain tip hash for the range, a detached signature over the manifest, and a public key reference Given the produced bundle and the platform verification tool When verification is executed Then signature verification succeeds using the referenced public key and the exit code is 0 Given any file or the manifest is altered after export When verification is executed Then verification fails, the report lists the mismatched or missing items, and the exit code is 1 Given the manifest includes a public key identifier and reference When verification runs Then the tool resolves the key from the reference and records the key id used in the verification report
Hash Chain Recompute via API/CLI Over Selected Range
Given a start and end ledger entry identifier When the verify command or API endpoint is invoked with that range Then the tool recomputes link hashes across the range and outputs the computed chain tip hash and a verified boolean Given the chain is intact for the provided range When recompute completes Then verified equals true and the exit code is 0 (CLI) or HTTP 200 with verified:true (API) Given a broken hash link exists within the range When recompute completes Then verified equals false, the report includes the first mismatching entry id with expected vs actual prev_hash, and the exit code is 1 (CLI) or HTTP 200 with verified:false (API) Given invalid or non-existent entry identifiers are provided When recompute is invoked Then an error is returned describing the bad parameter and the exit code is 2 (CLI) or HTTP 400 (API)
Latest Verified Chain Tip Indicator in UI
Given the background verification job last succeeded within the defined freshness window When an admin opens the Audit Ledger UI Then the chain tip indicator shows status Verified with tip height, tip hash, and timestamp matching the latest job metadata Given the last verification job failed When an admin opens the Audit Ledger UI Then the indicator shows Verification Failed, the timestamp of the last successful verification, and a link to details Given a user interacts with the indicator (hover or click) When the details are displayed Then the modal shows the last anchored external timestamp proof identifier and the corresponding chain tip hash Given no verification has ever run When an admin opens the Audit Ledger UI Then the indicator displays Not Yet Verified and provides a control to start verification
External Timestamp Anchoring and Proof Validation
Given a new chain tip is produced When the scheduled anchor job runs Then it submits the tip hash to the configured external timestamping service and stores the returned proof artifact and identifier Given an auditor obtains the stored anchor proof When they verify it against the external service using the documented procedure Then the proof validates and yields the original tip hash equal to the recorded chain tip Given the external timestamping service is unreachable When the anchor job runs Then the job retries with exponential backoff at least 5 times over a maximum window of 60 minutes and marks the anchor status as Deferred without blocking exports Given a previously anchored tip When verification is performed for that historical range Then the associated anchor proof remains retrievable and validates to the same tip hash
Automatic Blocking of New Exports on Verification Failure
Given a verification failure is detected by the API/CLI or scheduled job When the system flags the ledger integrity status as compromised Then subsequent export requests are rejected with HTTP 423 Locked in API and a UI message indicating Export blocked due to integrity verification failure, and the event is logged with actor and timestamp Given exports are currently blocked due to a prior failure When an administrator resolves the issue and triggers a successful re-verification Then exports are unblocked, the unblocking action is recorded with actor and timestamp, and a clearance notification is sent to administrators Given existing signed exports exist at the time of failure When users access those exports Then downloads remain available and the UI displays a warning banner identifying the potentially affected ranges
Public Key Reference and Rotation Support
Given an export bundle is created When inspecting the manifest Then it includes a public key reference with a key identifier (kid), a URL to key material (e.g., JWKS), and the signature algorithm used Given a key rotation occurs When new exports are created thereafter Then they are signed with the new key and the manifest reflects the new kid, while previously issued bundles remain verifiable using the key set published at the reference URL Given the referenced key material cannot be fetched during verification When the verification tool runs Then it fails with a clear network/key-resolution error and a non-zero exit code without falsely reporting verification success
Auditor Self-Service Verification Without Console Access
Given an auditor has only the export bundle, the public key reference, and the standalone verification tool When they follow the provided documentation Then they can verify signature validity, per-file checksums, and hash-chain integrity for a specified range on a clean machine without logging into Timeglue, resulting in exit code 0 and a verified:true report Given the documentation includes sample bundles and commands When the samples are executed as written Then the tool outputs match the expected results in the docs and exit code is 0 Given the auditor submits a malformed or incomplete bundle to the tool When verification runs Then the tool emits specific, actionable error messages identifying the problem and exits with a non-zero code Given the auditor prefers API-based validation When they POST the bundle manifest and range to the public verification endpoint Then the response includes verified:true/false, anomalies:[…], and anchor_status:(valid|invalid|not_found) with HTTP 200
Retention, Redaction, and Legal Hold
"As a data protection officer, I want configurable retention, redaction, and legal holds so that we meet compliance obligations without losing the ability to evidence decisions."
Description

Implement configurable retention policies per workspace to automatically purge or pseudonymize ledger records after defined periods while preserving evidentiary integrity via salted hashes or tokens. Support legal holds that suspend purging for specified scopes with audit trails capturing who applied the hold and why. Enable field-level redaction of sensitive SSO attributes in stored events and exports, marking redacted fields in manifests for transparency. Ensure data residency options and encrypted backups align with compliance requirements, and use object locking or logical WORM to prevent premature deletion. Provide admin UI and APIs to manage retention, redaction templates, and holds, and surface policy evaluations in export job summaries. All lifecycle actions are themselves auditable and reversible only within allowed policy windows.

Acceptance Criteria
Workspace Retention Policy Enforcement
Given a workspace with a configured 90-day retention policy and a pseudonymization template And ledger records exist older than 90 days, some under active legal hold and some not When the scheduled retention job runs Then records older than 90 days that are not on legal hold are pseudonymized or purged per the template And records on legal hold are not modified or deleted And a retention job audit event is recorded with counts processed, policy version, start/end timestamps, and system actor And salted integrity tokens allow verification that post-pseudonymization records still hash-chain correctly And re-running the job within the same window makes no additional changes (idempotent)
Legal Hold Application and Suspension of Purge
Given an admin with Hold Manager role selects a scope (workspace=ACME, time range=Jan–Mar, rule version=v5) and enters a mandatory reason When the admin applies a legal hold via UI or API Then all ledger records matching the scope are marked as on-hold within 5 seconds And retention purges/pseudonymization skip these records while the hold is active And an immutable audit event is created capturing who applied the hold, when, reason, and scope And attempting to remove the hold requires Hold Manager role and captures a separate audit event with reason And once the hold is released, the next retention job resumes processing only for records whose holds are cleared
Field-level Redaction of Sensitive SSO Attributes
Given a redaction template that masks fields [email, employee_id, department] with stable salted tokens And new ledger events include those SSO attributes When events are stored and later included in CSV/JSON exports Then the stored events contain redacted values per template and are tagged as redacted=true at the field level And export files contain the redacted values and a manifest that lists redacted fields and template version And attempts to view original values via UI/API return placeholders and 403 unless within an allowed reversal window defined by policy And all redaction actions create audit entries with actor, reason (policy), and affected field counts
Data Residency and Encrypted Backup Compliance
Given a workspace configured for EU residency and KMS-managed encryption keys When new ledger events are written and backups are taken Then all primary and backup objects are stored in EU-designated buckets/storage classes with region tags And data is encrypted at rest with the workspace’s KMS key and in transit with TLS 1.2+ And cross-region data movement is blocked by policy; export jobs for this workspace are executed from EU compute and tagged with region=EU in summaries And changing residency or keys requires Admin role, triggers revalidation, and emits an audit event capturing who, what, when, and before/after values
Object Locking / Logical WORM Protection
Given a ledger object under active retention with object locking enabled (WORM) until a computed expiry When any user or process attempts to delete or modify the object before expiry via UI or API Then the operation is denied with HTTP 409 and an error code indicating retention lock active And the UI displays the retention expiry timestamp and policy version And backups inherit WORM retention so that restore operations cannot circumvent the lock And all denied attempts are logged as security events with actor, timestamp, and request context
Admin UI and API Management of Policies and Templates
Given an Admin accesses the Retention & Redaction settings When the Admin creates, updates, versions, or deprecates a retention policy, redaction template, or legal hold Then inputs are validated (required fields, allowed scopes, version uniqueness), changes are previewed with impact estimates, and a confirmation step is required And successful operations return 201/200 with resource IDs and version numbers via API and show success banners in UI And RBAC is enforced: only qualified roles can perform each action; unauthorized attempts return 403 and are audited And every lifecycle change (create/update/publish/deprecate/delete-within-window) is captured in the audit ledger with diff summaries
Export Summary Shows Policy Evaluations
Given a user initiates a CSV/JSON export for a date range that includes on-hold records and redacted fields When the export completes Then the export summary includes counts of records exported, excluded due to legal hold, pseudonymized, and fields redacted And the summary lists policy and template versions applied, residency region, hash-chain verification status, and any exceptions And the exported files contain a manifest marking redacted fields and data lineage hashes And no unredacted sensitive SSO attributes appear in the payload; automated checks flag and fail the export if violations are detected

Access Request

Offer blocked visitors a streamlined override request with reason and context. Route to the right owner with SLAs, enable time-bounded approvals, and record the exception automatically—unblocking edge cases without weakening your guardrails.

Requirements

Blocked Page Request Form
"As a blocked invitee, I want to request an exception with clear context so that I can still meet when necessary without guessing what details the organizer needs."
Description

Display a streamlined override request form on the block screen when a proposed meeting violates guardrails (after-hours, holidays, focus blocks). Capture reason, desired time window, urgency, and contact details; pre-fill context from the shared link (meeting type, organizer, invitee email if known). Ensure responsive, accessible, and localized UI. Validate and persist submissions, then hand off to routing while preserving original guardrail settings. Allow link/team-level configuration of required fields and custom prompts.

Acceptance Criteria
Display form on block screen with pre-filled context
Given a visitor opens a Timeglue scheduling link that is blocked due to after-hours, holiday, or focus block When the block screen renders Then an override request form is displayed in place of the scheduling controls And meeting type, organizer name, organizer email, and invitee email (if present in link context) are pre-filled from link context And the desired time window defaults to the originally proposed slot converted to the invitee’s local time zone And no guardrail settings are modified or bypassed by the form’s presence
Field capture, validation, and configurable required fields
Given default configuration When a user submits with missing or invalid values Then inline errors are shown and submission is blocked for: reason, desired time window (start and end), urgency, and contact email Validation rules: - Email matches RFC 5322 simplified pattern and is ≤ 254 characters - Desired time window start ≥ current time; end > start; both include time zone; maximum span 12 hours - Reason length between 10 and 5000 characters; HTML tags are stripped or rejected - Urgency ∈ {Low, Normal, High} Given a team/link configuration that marks fields as required/optional and defines custom prompts When the form renders Then required fields display a required indicator, custom labels/placeholders are applied, and server-side validation enforces the configuration Given the invitee email is unknown in link context When the form renders Then the contact email field is empty and required by default
Accessibility compliance (WCAG 2.1 AA)
The form meets WCAG 2.1 AA: - All interactive controls are reachable and operable via keyboard (Tab/Shift+Tab/Enter/Space) - Visible focus indicators are present on all focusable elements - ARIA labels/roles provided for inputs and error messages; errors announced via aria-live - Color contrast ratios ≥ 4.5:1 for normal text and ≥ 3:1 for large text/icons Given a screen reader user navigates the form When errors occur on submit Then focus moves to the first error and the error text is announced Given a user has prefers-reduced-motion enabled When the form appears Then animations and motion effects are disabled
Localization and timezone display
Given a link includes a locale parameter When the form renders Then all static text, labels, validation messages, and date/time formats appear in that locale Given no locale parameter is provided When the form renders Then locale is inferred from the account default or falls back to the browser Accept-Language Supported locales defined by the workspace are selectable; unsupported locales fall back to English All date/times display in both organizer and invitee time zones with clear labels; user-editable inputs show the invitee’s local time with time zone abbreviation Numeric, date, and time formats follow CLDR patterns for the active locale
Responsive layout and performance
The form renders without horizontal scroll and with legible text at 320px, 390px, 768px, and 1280px widths Primary actions remain visible within one viewport on 390x844 and larger Cumulative Layout Shift ≤ 0.1; Largest Contentful Paint ≤ 2.5s; Time to Interactive ≤ 2.0s on a simulated slow 4G mobile profile Total additional compressed assets for the blocked page form ≤ 250KB; images are responsive and lazy-loaded
Submission persistence with guardrail snapshot
Given a valid submission When the user clicks Submit Then a request record is created with: unique ID, created_at (UTC), link/team identifier, locale, user-provided fields, pre-filled context, and the original guardrail settings snapshot (guardrail type, rule identifiers, violated constraints) And the record is durably stored and retrievable by ID; PII fields are encrypted at rest Given a network retry within 5 minutes with the same idempotency key When the server receives the request Then only one record exists and the response is identical (HTTP 201) Guardrail configuration on the scheduling link remains unchanged after submission
Routing handoff and user feedback
Given a valid submission is persisted When the system posts the payload to the routing service Then it receives HTTP 202 with a routing ticket ID And the user sees a confirmation state with the ticket ID, an SLA estimate, and a summary of submitted details Given the routing service is unavailable When the user submits Then the system retries with exponential backoff up to 3 times and displays a non-blocking banner; on final failure, the user sees a clear error with a retry option and support link, and the record remains queued for server-side retry At no point are guardrails disabled or the scheduling page unlocked by the submission
Intelligent Request Routing
"As a workspace admin, I want requests to reach the right approver automatically so that we respond fast without manual triage."
Description

Automatically route incoming access requests to the correct owner based on link, organizer, meeting type, and workspace rules (team owners, delegates, on-call). Deliver notifications via in-app inbox, email, and Slack with fallbacks and business-hours awareness. Support round-robin for team-owned links and attach full context so approvers can decide quickly. Provide workspace and link-level routing configuration integrated with Timeglue’s user, team, and permissions models.

Acceptance Criteria
Route to Owner by Link, Organizer, and Workspace Rules
Given a workspace has routing precedence configured as: Link > Meeting Type > Organizer Delegate > Team Owner > Default And link L1 is mapped to owner U2 When an access request arrives for link L1 with meeting type MTA and organizer U1 Then the request is assigned to U2 And the assignment event records requestId, assigneeId=U2, ruleMatched="link", timestamp Given link L2 has no owner and meeting type MTA is mapped to team T1 with owner U3 When an access request arrives for link L2 with meeting type MTA Then the request is assigned to U3 And ruleMatched="meetingType" Given link L3 has no owner and meeting type MTB has no mapping and organizer U4 has delegate U5 enabled by workspace rules When an access request arrives for link L3 with meeting type MTB from organizer U4 Then the request is assigned to U5 And ruleMatched="delegate" Given no routing rule matches for a request When the request is processed Then it is assigned to the workspace default approver group G1 And ruleMatched="default" And if G1 contains multiple approvers, the round-robin policy is applied
Round-Robin Assignment for Team-Owned Links
Given a team-owned link L4 with approver pool {A,B,C} and policy roundRobin=sequential When 9 requests are processed sequentially Then the assignments are A,B,C,A,B,C,A,B,C And no approver receives two consecutive assignments Given approver B is deactivated When the next request is processed Then the pool becomes {A,C} And the next assignment respects the lastAssigned pointer excluding B Given two requests arrive concurrently for L4 When assignments are computed Then both requests are not assigned to the same approver due to atomic locking And any conflict is retried until unique assignees are produced Given approver D is added to the pool When all current pool members have each received at least one assignment after D’s addition Then D is included in the rotation And the rotation remains fair such that over any window of N requests, each active approver receives either ⌊N/k⌋ or ⌈N/k⌉ assignments (k = active approvers)
Business-Hours and Holiday–Aware Notifications with Fallbacks
Given an approver has business hours 09:00–17:00, Mon–Fri, timezone America/New_York, and holiday calendar including 2025-11-27 When a request is assigned at 02:00 local time on a working day Then in-app inbox receives the item immediately And email/Slack notifications are deferred until 09:00 local time unless doing so would breach the SLA first-response time Given a request is assigned at 10:00 local time on a holiday When notifications are sent Then external notifications are deferred until the next business day 09:00 unless SLA would be breached sooner Given Slack delivery fails with a non-transient error When sending notifications Then email is sent within 10 seconds as a fallback And only one successful channel notification is delivered per channel (no duplicates) And all delivery attempts and deferrals are logged with channel, status, and timestamp
Multi-Channel Notification Delivery and Content Integrity
Given a request is assigned to approver U7 When notifications are generated Then in-app, email, and Slack payloads each include: requestId, linkId+name, requester name+email, organizer name, meeting type, requester timezone, proposed time window(s), request reason, SLA deadline timestamp, Approve and Deny action links/buttons, and a View Details link And the action links are single-use, expiring at SLA deadline or upon decision And notifications are delivered within 5 seconds for in-app and within 15 seconds for email/Slack under nominal conditions And message signatures or tokens validate authenticity server-side before accepting an approval/denial And channel-specific formatting differences do not omit any mandatory fields
Delegate and On-Call Overrides
Given owner U8 has delegate U9 active and U8 is marked Out of Office for the request timeframe When a request targeting U8 is processed Then the request is assigned to U9 And ruleMatched="delegate-OOO" Given no delegate is available and an on-call rotation R1 is configured for the workspace When a request cannot be assigned to the primary owner due to after-hours or OOO Then the request is assigned to the current on-call approver per R1 And the assignment event records onCallRosterId=R1 Given both delegate and on-call are available When routing occurs outside primary owner’s business hours Then delegate is preferred if configured to receive after-hours, otherwise route to on-call per workspace policy And the decision path is recorded in audit logs
Workspace and Link-Level Routing Configuration
Given a workspace admin opens routing settings When they configure precedence, default approver group, business-hours policy, and fallbacks Then the configuration is validated (no circular precedence, valid users/teams/groups) and saved And changes are audited with actor, before/after, and timestamp Given a link owner updates link-level routing (owner, approver pool, round-robin policy) When they save changes Then only users with permission link.configure_routing can save And the new rules apply to new requests created after the save time And existing requests are unaffected Given conflicting rules exist (e.g., link-level owner vs workspace meeting-type owner) When routing executes Then precedence determines the rule used And the chosen rule is recorded as ruleMatched in the assignment event
SLA-Based Escalation and Auto-Reassignment
Given SLA first-response is 2 hours for access requests When 1 hour passes without an action from the assignee Then a reminder is sent on all configured channels within business-hours policy Given the SLA deadline is reached without action When escalation triggers Then the request is reassigned to the next eligible approver (delegate, on-call, or group round-robin per routing rules) And an escalation notification is sent to the new assignee and watchers And the audit log records previous assignee, new assignee, escalationReason="SLA_BREACH", and timestamps Given a reassignment occurs due to SLA breach When metrics are computed Then the request is counted toward SLA breach metrics and time-to-first-response dashboards
SLA Timers and Escalations
"As a team lead, I want time-bound commitments and escalations so that urgent requests don’t stall and stakeholders know what to expect."
Description

Allow admins to define response SLAs per team or link (e.g., respond within 4 business hours). Start timers on submission, show countdown in approver UI, send reminders, and escalate to backup approvers when thresholds are breached. Auto-decline with a customizable message if SLA expires. Track all events for reporting and include SLA adherence in analytics.

Acceptance Criteria
SLA Configuration and Precedence
- Given I am an admin, When I set a team-level response SLA to 4 business hours with timezone "US/Pacific" and save, Then the setting is persisted and displays as "4 business hours (US/Pacific)" in team settings. - Given a link has its own SLA of 2 business hours, When a request is submitted via that link, Then the link-level SLA overrides the team-level SLA. - Given Team A uses the "US Holidays" calendar, When SLA time is calculated, Then holidays and non-business hours are excluded from elapsed time. - Given validation rules, When I attempt to save an SLA < 15 minutes or > 168 hours, Then the form blocks save and shows a validation error. - Given I change the SLA timezone, When a new request is submitted, Then its SLA uses the new timezone; existing open requests continue using the timezone captured at submission. - Given auto-decline on expiry is enabled, When an escalation threshold is configured to 100%, Then the system blocks the change and instructs the admin to choose a threshold < 100% to avoid conflict.
Timer Start and Countdown Display
- Given an access request is submitted during business hours, When the submission is recorded, Then the SLA timer starts within 5 seconds and stores a server-side start timestamp. - Given the request crosses into non-business hours, When the approver UI displays the countdown, Then remaining time pauses during non-business hours and resumes at next business open. - Given an approver opens the Approver UI, When the page loads, Then a visible countdown (HH:MM) shows time remaining and updates at least once per minute without page refresh. - Given client clock skew, When the page loads or refreshes, Then countdown is based on server time and remains accurate across devices and reloads. - Given temporary network loss, When connectivity is restored, Then the countdown resynchronizes within 2 seconds.
Reminder Notifications Before Breach
- Given reminders are configured at 50% and 90% of SLA consumption, When those thresholds are reached without a decision, Then primary approvers receive email and in-app notifications within 60 seconds. - Given a reminder was sent, When any approver takes action (approve/decline/comment), Then all future reminders for that request are suppressed. - Given a reminder delivery fails, When retries are attempted, Then up to 3 retries occur with exponential backoff and failures are logged as events. - Given multiple primary approvers, When a reminder is sent, Then each approver receives a single reminder and duplicate sends are prevented.
Escalation to Backup Approvers
- Given backup approvers are configured and an escalation threshold is set to 90% of SLA, When the threshold is reached without action, Then the request escalates and backup approvers are notified within 60 seconds. - Given an escalation occurs, When a backup approver acts, Then the request is marked decided and primary approvers are prevented from further action. - Given multiple escalation levels (L1 then L2) are configured, When L1 remains undecided for its escalation window, Then escalate once to L2 and do not re-trigger L1. - Given no backup approvers are configured, When the escalation threshold is reached, Then the system records a skipped escalation event and proceeds toward expiry handling.
Auto-Decline on SLA Expiry
- Given auto-decline on expiry is enabled, When the SLA timer reaches 0 with no decision, Then the request is auto-declined within 60 seconds. - Given the auto-decline fires, When notifying the requester, Then the customizable message template (supporting {request_id}, {link_name}, {sla_hours}, {reason}) is sent via email and added to the request thread. - Given a near-simultaneous decision, When an approver action and the expiry occur at the same second, Then the server timestamps decide precedence and a recorded decision at or before expiry prevents auto-decline. - Given an auto-decline occurred, When an approver attempts action afterward, Then the UI blocks the action and shows "Request auto-declined due to SLA expiry".
Event Tracking and Auditability
- Given any SLA-related event (submission, reminder, escalation, decision, expiry, auto-decline), When it occurs, Then an event is recorded with event_type, request_id, actor_id (nullable), timestamp (UTC), and sla_remaining_ms. - Given an audit export is requested for a date range, When the export is generated, Then all SLA events are included and filterable by team, link, approver, and SLA outcome (within, breached, expired). - Given a request timeline is viewed, When opened, Then SLA events appear in chronological order with human-readable labels and exact timestamps.
Analytics: SLA Adherence Metrics
- Given analytics run nightly and on-demand, When the SLA dashboard is viewed, Then it displays % of requests responded within SLA, average/median/95th percentile first response (business hours), and count of escalations. - Given filters for time range, team, link, and approver, When filters are applied, Then metrics and charts refresh within 3 seconds and reflect the selected scope. - Given per-request SLA timezones, When charts are rendered, Then business-hour calculations use each request's SLA timezone and are labeled accordingly. - Given a user drills into "Breached" requests, When selecting that segment, Then a list populates with request_id, SLA target, time to first action, number of reminders, and highest escalation level reached.
Time-Bounded Approval Rules
"As an approver, I want to approve narrowly scoped exceptions so that we unblock critical meetings without weakening our standard guardrails."
Description

Enable approvers to grant narrowly scoped overrides: one-time slot, limited date range, recurring window, or limited count; optionally constrain by participants. Set automatic expiry and revocation. Apply overrides that temporarily relax only the necessary guardrails (e.g., extend work hours by 30 minutes) without changing baseline settings. Enforce constraints during scheduling and link the exception to the resulting meeting.

Acceptance Criteria
One-Time Slot Override with Auto-Expiry
- Given an approver creates a one-time override for date D with window [T1–T2] that relaxes work hours by +X minutes, When a user attempts to schedule a meeting that starts within [T1–T2] on D, Then the scheduler allows the booking and records that the override was applied. - Given the same override, When a user attempts to schedule outside [T1–T2] on D, Then the scheduler blocks the booking due to guardrails. - Given the same override, When the meeting is created, Then the override is marked consumed and cannot be reused. - Given the current time is after T2 on D and the override is unused, When a booking is attempted, Then the override is auto-revoked and the booking is blocked.
Date Range Override with Daily Window Enforcement
- Given an approver creates a date-range override valid from D1 to D2 with daily window [Wstart–Wend] (evaluated in the policy timezone), When a user schedules a meeting that starts within [Wstart–Wend] on any date D ∈ [D1, D2], Then the scheduler allows the booking. - Given the same override, When a user attempts to schedule outside [Wstart–Wend] or on a date outside [D1, D2], Then the booking is blocked. - When the current date is after D2 or the current time is after Wend on D2, Then the override auto-expires and is inactive for any further bookings.
Recurring Weekly Window with End Date
- Given an approver creates a recurring override with weekday pattern P (e.g., Tue/Thu) from time [Rstart–Rend] until end date Dend, When a user schedules a meeting that starts during an occurrence in pattern P before or on Dend, Then the scheduler allows the booking. - Given the same override, When a user attempts to schedule on a weekday not in pattern P or after Dend, Then the booking is blocked. - Given a date in pattern P is also a company holiday and the override relaxes only work hours (not holidays), When a booking is attempted in [Rstart–Rend], Then the booking is blocked by the holiday guardrail.
Limited Count Override Consumption and Revocation
- Given an approver creates an override with maximum uses N (>0) and an active scope (time window or recurrence), When meetings are created within scope, Then each successful meeting creation decrements the remaining count by 1 at commit time. - Given two concurrent booking attempts when remaining count is 1, When both attempt to create within scope, Then at most one succeeds and the other is rejected due to no remaining uses. - When remaining uses reach 0 or the override end time passes (whichever comes first), Then the override auto-revokes and cannot be applied to new bookings.
Participant-Constrained Override Validation
- Given an approver creates an override constrained to participants {p1, p2, ...}, When a user schedules a meeting within the override’s time scope that includes all of {p1, p2, ...}, Then the scheduler allows the booking. - Given the same override, When a booking within time scope omits any required participant from {p1, p2, ...}, Then the booking is blocked. - Given the same override, When additional participants beyond {p1, p2, ...} are included, Then the booking is allowed provided time scope is satisfied. - Participant matching is performed by verified user ID/email equivalence; aliases map to the same identity.
Exception Linkage and Guardrail Scope Integrity
- Given a booking succeeds using an override, When the meeting is created, Then the meeting record contains override_id, approver_id, relaxed_guardrails, scope (type, dates/times, participants), created_at, and expiry, retrievable via UI and API. - Given an override that relaxes only work hours, When a booking attempt conflicts with a holiday or focus block, Then the booking is blocked by those guardrails (i.e., only specified guardrails are relaxed). - Given an override has expired or been consumed, When a user attempts any booking that would require that override, Then the booking is blocked and the baseline settings remain unchanged. - Given a meeting linked to an override is rescheduled, When the new start time is outside the override scope, Then the reschedule is rejected unless another applicable override exists.
One-Click Approver Actions
"As a busy manager, I want to approve or decline requests quickly from wherever I am so that decisions don’t wait for me to open the app."
Description

Provide actionable notifications and a compact approval UI in Slack, email, and the web app to approve/deny with one click, preview conflicts, propose alternative windows, and add notes. Sync actions in real time across channels and automatically update the requester with the outcome and next steps. Include safety checks that highlight after-hours or focus-block impacts before approval.

Acceptance Criteria
Slack One-Click Approval with Safety Check
Given a pending access request assigned to the approver and an actionable Slack message is visible When the approver clicks Approve Then a confirmation modal appears showing requester/approver local times, after-hours indicator, focus-block overlaps, holiday conflicts, and an optional note field (0–500 chars) And the Confirm button remains disabled until the modal fully loads and any required acknowledgement is checked And when the approver confirms, the request status changes to Approved within 2 seconds, the modal closes, Slack buttons are replaced with a read-only Approved state with approver name/timestamp, the requester is queued for notification, and an audit log entry captures actor, channel=Slack, note, and safety-check snapshot
Email One-Click Denial with Reason
Given a pending access request and an actionable email delivered to the approver When the approver clicks Deny in the email Then a secure confirmation page opens showing the request summary and a required reason field (minimum 10 characters), protected against CSRF and link tampering And upon confirm, the request status changes to Denied within 5 seconds, the email action link becomes single-use (subsequent clicks render Already processed), an audit log entry records actor, channel=Email, and reason, and the requester is notified with the denial reason and next steps
Web Compact Approval UI with Conflict Preview and Alternative Proposal
Given an approver opens the request in the web app’s compact approval panel When the panel loads Then it displays request details and a conflict preview summarizing overlapping focus blocks, after-hours flags, existing meetings, and holidays with counts and time zones And a Propose alternative control allows selecting a time window via draggable timeline or picker constrained to business hours When the approver submits a proposed window with an optional note Then the request transitions to Change Proposed, the requester is notified with the proposed window and a link to accept or counter, and no approval is recorded until accepted
Real-Time Cross-Channel Sync and Stale Action Handling
Given an approver takes an action (Approve, Deny, or Propose Alternative) in any channel (Slack, Email, Web) When the action is committed Then Slack messages, the web panel, and actionable email state reflect the new status within 2 seconds and disable or replace action buttons accordingly And repeated clicks from stale messages are idempotently rejected with a non-destructive notice, no duplicate state transitions occur, and only one audit log entry is created
Safety Check Acknowledgement for After-Hours/Focus Impacts
Given the safety check detects that approving will impact after-hours or a participant’s focus block When the approver attempts to approve in any channel Then the UI requires explicit acknowledgement via a checkbox labeled acknowledging the impact before enabling approval And if organizational policy forbids after-hours approvals, the Approve action is blocked with a policy message and only Propose alternative remains enabled
Automatic Outcome Notifications with Next Steps
Given a request transitions to Approved, Denied, or Change Proposed When the state changes Then the requester receives a notification via their preferred channel(s) within 5 seconds containing the outcome, approver note, localized times, and next steps (e.g., Book within approved window, Review proposed times, or Reason for denial) And the notification threads to the original Slack conversation or uses a consistent email subject including the reference ID And delivery failures are retried with exponential backoff up to 3 attempts and surfaced in admin logs
Exception Audit Logging
"As a compliance officer, I want comprehensive logs of exceptions so that we can demonstrate policy adherence and investigate issues."
Description

Record an immutable audit trail for each request: requester, approver, timestamps, reason, context, constraints applied, outcome, and notifications sent. Store exceptions as first-class records linked to users, links, and meetings. Provide an admin view with filters, search, and export, and surface metrics such as volume, approval rate, and SLA adherence. Respect workspace data retention policies.

Acceptance Criteria
Create and Append Audit Events for Access Request Lifecycle
- Given a blocked visitor submits an access request with reason, context, and link, When the request is received, Then an audit event is created with fields: request_id, requester_id/email, link_id, meeting_id (nullable), submitted_at (UTC), reason, context, constraints_proposed (nullable), status='pending', and source_ip/user_agent. - Given an approver approves, denies, or the request expires, When the decision or expiry is recorded, Then a new audit event is appended (no in-place update) with approver_id (nullable if expired), decision ('approved'|'denied'|'expired'), decided_at (UTC), constraints_applied (if approved), and final_outcome matching decision. - Given the request state changes, When events are appended, Then previous events remain immutable and a monotonic sequence_number increases by 1 for the request.
Immutability and Tamper Evidence for Audit Log
- Given the audit datastore, When an attempt is made to modify or delete an existing audit event, Then the operation is rejected and logged, and only append operations are permitted via service account roles. - Given an audit event is written, When the hourly integrity verifier runs, Then the event’s hash and previous_hash form a valid chain; any break produces a P1 alert and a new audit event recording integrity_failure=true. - Given an admin requests raw audit payload, When the event is fetched, Then it contains immutable_id, created_at (UTC), hash, previous_hash, writer_service_id, and signature fields.
First-Class Exception Records and Referential Links
- Given an access request is approved, When the approval is saved, Then a first-class Exception record is created with exception_id, request_id, requester_id, approver_id, link_id, meeting_id (nullable), window_start/window_end (UTC), constraints_applied, status='active'. - Given the Exception record exists, When querying by user, link, or meeting, Then the record is returned in each scope and referential integrity is enforced (no dangling foreign keys). - Given an extension or revocation occurs, When processed, Then a new version is appended with version_number incremented and previous_version_id set; the audit log records the change_type and delta fields.
Admin Audit View: Filters, Search, Pagination, Export, and Access Control
- Given the admin audit view, When filters for date range, requester, approver, outcome, SLA status, and reason keyword are applied, Then results match all filters and return within ≤2s for up to 50k records with server-side pagination (page size configurable up to 200). - Given the search box, When a user searches by request_id, email, link slug, or free-text reason, Then case-insensitive partial matches are returned with the query echoed and hit counts correct. - Given an authorized workspace admin clicks Export CSV on the current result set, When export is requested, Then a CSV with predefined columns is generated asynchronously within ≤60s for up to 1M rows; the export job and file URL are logged as audit events. - Given a non-admin or a user from another workspace accesses the view, When they attempt to load data, Then a 403 is returned and zero records from other workspaces are exposed.
Metrics: Volume, Approval Rate, and SLA Adherence
- Given a selected reporting window, When metrics are computed, Then volume equals count(requests.created_at within window), approval_rate equals approved/(approved+denied) within window, and SLA_adherence equals percent of resolved requests meeting their SLA target; values match a verified fixture within ±0.1%. - Given a workspace display time zone is set, When the user changes it, Then charts re-render in the selected time zone while calculations remain in UTC and totals do not change. - Given filters by workspace, link, team, or approver are applied, When metrics refresh, Then values reflect only filtered records and agree with a drill-down of underlying rows.
Notification Events for Requests and Decisions
- Given a notification is triggered by submission, decision, or SLA reminder, When the provider responds, Then an audit event is appended with notification_id, request_id, type, channel, recipients (masked), template_id, sent_at (UTC), provider_message_id (nullable), delivery_status, error_code (nullable), and retry_count. - Given duplicate sends are attempted, When the same idempotency_key is reused within 24h, Then no duplicate notification event is created and an audit event records deduped=true referencing the original notification_id.
Data Retention and Legal Hold Compliance
- Given a workspace retention policy of N days, When an audit event exceeds N days without legal hold, Then its PII fields are purged or anonymized per policy within 24h and a retention_action audit event is appended; aggregate metrics remain unchanged within ±0.1%. - Given a legal hold is placed on a user or request, When retention jobs run, Then events under hold are excluded from purge until the hold is removed and the hold status is visible in the admin view and export. - Given exports or searches include purged records, When results are returned, Then redacted fields display as null or 'REDACTED' consistently and no raw PII is exposed.
Security and Abuse Protection
"As a workspace admin, I want safeguards against spam and misuse so that opening an exception path doesn’t create security or compliance risk."
Description

Protect the request surface with rate limiting, CAPTCHA, email/domain verification, and spam heuristics. Redact sensitive data in notifications by default with opt-in reveal for approvers. Enforce least-privilege access to request data, encrypt data in transit and at rest, and honor retention and deletion policies. Provide a block-list for abusive senders and throttle repeated denials.

Acceptance Criteria
Rate Limiting, Block-List, and Denial Throttling
Given a client IP issues more than 100 POST /access-requests in 10 minutes When the 101st request is received Then respond 429 with a Retry-After header of at least 60 seconds and record event "RATE_LIMIT_IP" Given the same email address submits more than 5 access requests in 60 minutes When the 6th request is submitted Then respond 429 with code "RATE_LIMIT_EMAIL" and do not notify approvers Given a sender email or domain matches the block-list When a request is submitted Then respond 403 with code "BLOCK_LISTED" and persist no request content Given a requester has 3 denials within 24 hours When they attempt another submission Then enforce a 12-hour cooldown returning 429 with code "DENIAL_THROTTLE" Given a prior rate limit window has elapsed When the next valid request is submitted Then accept the request with 200/202 and clear temporary throttle state
Adaptive CAPTCHA Challenge for High-Risk Submissions
Given an unauthenticated visitor has spam score between 0.6 and 0.89 or IP usage is ≥80% of rate limit When they submit the form Then present a CAPTCHA challenge prior to submission Given a CAPTCHA response token is received When validated server-side within 120 seconds of issuance Then proceed with submission; else respond 403 with code "CAPTCHA_FAILED" Given an accessibility alternative is requested When CAPTCHA is required Then provide an audio or accessible challenge verified by the same validator Given a visitor successfully completes CAPTCHA When they submit again from the same browser and IP within 15 minutes and risk < 0.9 Then do not require another CAPTCHA
Email and Domain Verification Before Routing
Given a submission includes an email address When the form is posted Then send a verification email containing a single-use token link that expires in 15 minutes Given the verification link is opened before expiry When the token is validated Then mark the request "Email Verified" and enable routing to approvers Given the token is invalid or expired When the link is used Then respond 401 with code "EMAIL_VERIFY_FAILED" and allow a resend after a 2-minute cooldown Given the email domain is on a denylist or is disposable When the form is posted Then reject with 403 "DOMAIN_BLOCKED" and do not notify approvers Given the email domain lacks valid MX records When the form is posted Then reject with 422 "INVALID_EMAIL_DOMAIN"
Spam Heuristics Scoring and Enforcement
Given a submission is received When processed by the spam heuristics engine producing a risk score in [0.0, 1.0] Then if score ≥ 0.9, reject with 422 code "SPAM_BLOCK" and do not notify approvers; if 0.6 ≤ score < 0.9, require CAPTCHA; if score < 0.6, proceed without friction Given a spam decision (block or challenge) is made When the request is finalized Then log score, contributing signals, decision, request ID, and sender fingerprint under audit event "SPAM_DECISION"
Default Redaction with Approver-Triggered Reveal
Given a request contains sensitive fields (message body, attachments, phone numbers, emails, URLs) When generating notifications (email, Slack) or UI listings Then replace sensitive values with "[REDACTED]" by default Given an approver views a request When they click "Reveal" for a specific field Then display the full value for that field for 30 minutes in their session only and log audit event "SENSITIVE_REVEAL" with user ID, field name, timestamp, and request ID Given no reveal has occurred When approvers open the request details Then sensitive values remain redacted in UI, API responses, and webhooks Given an API client requests details When the caller lacks approver scope or does not include include_sensitive=true Then return redacted values and do not include attachments content
Least-Privilege Access Controls for Request Data
Given role-based scopes are configured (viewer, approver, owner, admin) When a user without approver or owner scope requests full request content or attachments Then respond 403 with code "INSUFFICIENT_SCOPE" and show only metadata (status, timestamps, requester domain) Given a user has approver scope for Workspace A only When they attempt to access requests from Workspace B Then respond 403/404 and log audit event "ACCESS_DENIED" with target workspace and user ID Given access is granted When a user views, downloads, or exports request content Then create an audit trail entry with user ID, action, timestamp, and request ID Given API endpoints are called with tokens lacking required scopes When the server evaluates the request Then enforce identical access rules as the UI and do not leak sensitive fields
Encryption, Retention, and Deletion Policy Enforcement
Given any client-server communication to Access Request endpoints When a request is made Then enforce TLS 1.2+ with HSTS and reject plaintext HTTP with 400 code "HTTPS_REQUIRED" Given request data is persisted (database, object storage, search index) When stored at rest Then encrypt using AES-256 or stronger with keys managed by a KMS and rotate keys at least every 90 days Given a workspace retention policy is configured (default 180 days; min 7, max 365) When a request reaches its retention end Then purge primary records within 24 hours and remove from indexes within the next 24 hours, logging event "RETENTION_PURGE" Given an admin initiates manual deletion When confirmed Then hard-delete primary records within 15 minutes, purge derived copies and attachments within 24 hours, ensure backup pruning within 30 days, and log event "MANUAL_DELETE" Given exports or notifications include request data When generated or transmitted Then ensure encryption in transit and at rest applies to those artifacts

Smart Fill

AI‑assisted painting that snaps your rough strokes to the safest overlap blocks automatically. It expands or trims selections to minimize after‑hours impact and respect work hours, holidays, and focus blocks—so you get optimal windows in seconds without pixel‑perfect tinkering.

Requirements

Constraint-Aware Selection Engine
"As a remote team lead, I want my rough timeline strokes to automatically snap to the safest overlap windows so that I can schedule fair, on-hours meetings without manual time zone math."
Description

Implements the core AI optimization that expands or trims a user’s rough painted strokes into the safest overlap windows across participants. The engine ingests each participant’s work hours, holidays, and focus blocks from Timeglue’s availability model, applies configurable penalties for after-hours impact, and returns contiguous time blocks that meet minimum duration and buffer constraints. It must handle multi-region scenarios, resolve daylight saving shifts, and prioritize windows that minimize total off-hours minutes. Outputs are deterministic for identical inputs, support tie-breaking rules (e.g., fewest people partially impacted), and return confidence scores to downstream UI components. The engine exposes an internal API used by the timeline canvas and smart link generation, enabling consistent behavior across create/edit flows.

Acceptance Criteria
Hard Constraints, Minimum Duration, and Buffers
Given participants with defined work hours, holidays, and focus blocks, and constraints minDuration=D, preBuffer=Bp, postBuffer=Bs And a set of rough painted strokes within a time-range T When the engine computes contiguous windows Then every returned window has duration >= D And for each participant, the intervals [start−Bp,start) and (end,end+Bs] are free of holidays and focus blocks And no returned window overlaps any holiday or focus block for any participant And every returned window lies within T And all returned windows are contiguous intervals without gaps
Global Optimization and Deterministic Tie-Breaking
Given a fixed set of participants, strokes, and penalty weights When the engine selects windows Then the chosen set minimizes total off-hours minutes across participants subject to the hard constraints And for identical inputs, repeated runs return identical windows and identical ordering And if two candidate windows have equal total off-hours minutes, the engine prefers the one with fewer partially impacted participants And if still tied, the engine prefers the earliest start time in UTC
Multi-Region and Daylight Saving Safe Calculations
Given participants across multiple time zones, including at least one undergoing a DST transition during the search window When computing windows that straddle the DST change Then each window respects each participant’s local work hours on the correct local dates And no window includes nonexistent or ambiguous local times for any participant And UTC start/end times are contiguous without unintended 60-minute gaps or overlaps And off-hours minutes are computed using each participant’s local work hours on their local calendar
Confidence Scores for Returned Windows
Given any set of inputs When the engine returns windows Then each window includes a confidenceScore in the range [0.0, 1.0] And for identical inputs, confidenceScore values are deterministic And if Window A has strictly lower total off-hours minutes than Window B and equal participant coverage, then confidenceScore(A) > confidenceScore(B) And windows with identical optimization metrics have equal confidenceScore within a tolerance of 0.001
Consistent Results Across Canvas and Smart Link Flows
Given the same participants, constraints, strokes, and engine version When invoked via the timeline canvas and via smart link generation Then both invocations return identical windows, confidence scores, and ordering
No-Feasible-Window Handling
Given constraints that make it impossible to satisfy minDuration and buffer requirements without violating holidays or focus blocks When the engine runs Then it returns an empty list of windows And the call completes without errors And repeated runs with identical inputs consistently return an empty list
Configurable Penalties and Constraint Parameters
Given per-call parameters for penalty weights, minDuration, and buffers When the engine runs Then it applies the provided parameters to optimization And omitting any optional parameter results in using documented defaults And invalid parameter values (e.g., negative durations, unknown tie-breaker key) are rejected with a validation error before optimization And increasing the after-hours penalty weight does not increase the total off-hours minutes of the top-ranked window for the same inputs
Real-time Snap-to-Overlap Feedback
"As a scheduler, I want instant, visual snapping feedback while I paint so that I can finalize optimal windows quickly without pixel-perfect adjustments."
Description

Provides sub-200ms interactive snapping as the user paints on the draggable timeline. As the pointer moves, the UI calls the constraint engine with incremental selections, rendering a preview overlay that shows the exact snapped block boundaries, duration, and impact chips (e.g., +0 after-hours, 1 minor conflict). Includes smooth animations, hover tooltips, and keyboard nudge controls for fine adjustments. If network latency spikes, the component falls back to local heuristics and reconciles when the authoritative result returns. Integrates with existing timeline zoom levels and supports both single and multi-day ranges.

Acceptance Criteria
Sub-200ms Snap Feedback While Painting
Given a user is dragging to paint on the timeline When the pointer moves Then a snapped preview overlay is rendered within 200ms of the pointer event for at least 95% of moves and <=120ms median over a 30s continuous drag And the overlay reflects the latest pointer position with no frame older than 200ms And during the drag there is no more than 1 dropped frame per second (>=95% frames at <=16.7ms)
Accurate Snapped Boundaries, Duration, and Impact Chips
Given work hours, holidays, and focus blocks are configured for all invitees When the user paints a rough selection intersecting constraint edges Then the snapped overlay shows start/end times aligned to the nearest safe overlap block with minute-level accuracy And the duration badge reflects the snapped range to the minute And impact chips show after-hours minutes per group and the count of minor conflicts, updating within 200ms after any adjustment And a fully safe window displays “+0 after-hours” and “0 conflicts”
Incremental Constraint Engine Calls and Coalescing
Given pointer move events during a drag at up to 60Hz When the user drags across the timeline Then the UI issues incremental selection requests to the constraint engine at a rate between 30Hz and 60Hz, coalescing intermediate events as needed And each request includes current start and end candidates and the active zoom level And when the drag ends a final authoritative request is sent within 50ms for the last pointer position
Smooth Overlay Animations Across Snaps and Zoom Levels
Given timeline zoom levels of 15m, 30m, 60m, and day view When snapped boundaries change during interaction Then overlay position and size animate to the new boundaries within 150ms using easing without flicker And overlay edges align to timeline ticks at the current zoom with <=1px deviation And when the zoom level changes mid-selection the overlay preserves start/end timestamps and re-renders within 200ms And when a selection spans multiple days or crosses a DST boundary timestamps remain correct and segments render contiguously across days
Hover Tooltips for Edges, Duration, and Impact Chips
Given the user hovers an overlay edge, duration badge, or impact chip for >=300ms When hover is sustained Then a tooltip appears within 150ms showing start and end timestamps with timezone abbreviation, total duration, per-participant after-hours minutes, and listed minor conflicts And tooltips are reachable via Tab/Shift+Tab and dismiss with Escape within 50ms And tooltip content reflects the latest snapped state with no value older than 200ms
Keyboard Nudge Adjustments with Snapping
Given a snapped selection is active When the user presses ArrowLeft or ArrowRight Then the nearest handle to the caret nudges by 1 minute and re-snaps to the closest compliant boundary And holding Shift nudges by 5 minutes; holding Alt/Option nudges by 15 minutes And updates render within 150ms and update duration and impact chips And nudges respect a minimum duration of 15 minutes and a maximum of 12 hours; violations snap to the nearest compliant boundary without crossing into a new day unless multi-day is enabled
Latency Spike Fallback and Reconciliation
Given network RTT exceeds 200ms or a response is not received within 150ms during interaction When the user continues dragging or nudging Then the component switches to local heuristic snapping and marks the overlay as estimating And upon receiving the authoritative result the overlay reconciles within 500ms via a smooth animation to authoritative boundaries and badges/tooltips update atomically And discrepancies >2 minutes between heuristic and authoritative results are logged with session and latency metrics
Safety & Preference Controls
"As a team lead, I want to set safety and duration preferences so that Smart Fill reflects my team’s standards and avoids unfair scheduling."
Description

Adds user-configurable controls that influence Smart Fill decisions: acceptable after-hours tolerance per participant role, minimum/maximum meeting duration, required buffers before/after, earliest/latest local times, and preference presets (e.g., "Zero After-Hours", "Balanced", "EU-Friendly"). Preferences can be saved at workspace, team, and link-template levels, and are respected by Smart Links. Validation prevents contradictory settings and surfaces conflicts proactively. The UI exposes sliders and toggles, while the engine consumes a normalized policy object for consistent application.

Acceptance Criteria
Role-Based After-Hours Tolerance Guides Smart Fill
Given participants with roles and configured per-role after-hours tolerances and defined work hours, holidays, and focus blocks When Smart Fill generates candidate meeting windows from a rough selection Then each returned window has per-participant after-hours minutes less than or equal to the tolerance for that participant’s role And the default suggested window is the one that minimizes the sum of after-hours minutes across participants And if no window satisfies all tolerances, no windows are returned and a conflict message states "No window within after-hours tolerances"
Min/Max Meeting Duration Enforcement
Given a meeting policy with minimum duration and maximum duration configured When Smart Fill proposes windows Then every suggested window duration is within the inclusive [min, max] range And if the rough selection is shorter than the minimum, Smart Fill expands the window to at least the minimum without violating other policies; otherwise it surfaces a conflict And if no window can meet the minimum within constraints, an error "Cannot meet minimum duration" is displayed and no window is applied
Required Buffers Before/After Meeting
Given required buffers before and after the meeting are configured When Smart Fill computes a suggested window Then the pre- and post-meeting buffers do not overlap any participant’s events or focus blocks and remain within earliest/latest and work-hour bounds And buffer constraints are included in any conflict explanations when violated And the buffers are persisted in the policy and applied to the final booking creation
Earliest/Latest Local Time Boundaries Respected
Given each participant has earliest and latest allowable local times and holiday calendars When Smart Fill computes overlap windows Then no window starts before a participant’s earliest or ends after the participant’s latest unless counted as after-hours within the participant’s role tolerance And any minutes outside earliest/latest are counted toward that participant’s after-hours minutes And holidays are treated as non-working days (zero working hours) unless explicitly allowed by settings
Preference Presets Apply and Persist Across Scopes
Given preset options (e.g., Zero After-Hours, Balanced, EU-Friendly) are available with stored definitions When a workspace admin applies a preset at the workspace level and saves Then new teams and templates inherit the preset values by default And when a team owner overrides any value, the team-level values take precedence for that team and its templates And when a Smart Link is generated from a template, the link’s effective policy equals the resolved scope values And booking via the Smart Link offers no slots that violate the effective policy And selecting a preset updates all related sliders/toggles to the preset values and the state is labeled with the preset name; modifying any value marks the state as Custom
Contradictory Settings Validation and Conflict Surfacing
Given invalid combinations exist (e.g., min duration > max duration, earliest >= latest, buffers exceed available time, negative tolerances) When the user attempts to save the preferences Then inline validation messages appear next to each offending field describing the issue And the Save action is disabled until all blocking errors are resolved And a consolidated conflict banner lists all contradictions with plain-language explanations And resolving the issues clears messages in real time and enables Save
Policy Object Normalization and Engine Consumption
Given the user adjusts preferences via sliders and toggles When the preferences are saved Then a normalized policy JSON conforming to schema timeglue.policy.v1 is produced with fields for durations, buffers, afterHoursToleranceByRole, earliestLatestByParticipant, holidaysBehavior, presetRef, scope, and version And the Smart Fill engine receives this policy, logs the schema version, and uses it to compute windows And engine outputs (windows and conflicts) reference the policy version used And reloading a saved template reconstructs the UI from the policy JSON with identical values
Calendar & Policy Ingestion
"As an organizer, I want Smart Fill to respect real calendar constraints so that suggested windows never ignore holidays, OOO, or focus blocks."
Description

Synchronizes the data Smart Fill relies on: working hours, holidays, OOO events, and focus blocks from connected calendars (Google, Microsoft) and Timeglue workspace policies. Includes deduplication and precedence rules (e.g., explicit OOO overrides working hours), time zone resolution, and daylight saving handling. Provides a caching layer with freshness indicators and graceful degradation if a source is temporarily unavailable. Emits a normalized availability graph consumable by the constraint engine and exposes audit logs for debugging unexpected suggestions.

Acceptance Criteria
Initial Sync: Ingest Calendars and Workspace Policies
Given a user has connected Google Calendar and Microsoft 365 Calendar and is a member of a Timeglue workspace with active working-hours and holiday policies When the ingestion service runs an initial full sync for that user Then it fetches working hours, holidays, OOO events, and focus blocks from all available sources within the workspace-configured scheduling horizon And it normalizes all events into a unified timeline using each event’s source timezone And it emits a normalized availability graph with ISO 8601 UTC start/end timestamps, non-overlapping intervals, and a reason code per interval And the response includes metadata: lastSyncedAt (UTC), sourcesProcessed, and per-source status codes
Precedence: OOO Overrides Work Hours and Focus Blocks
Given an OOO event overlaps a user’s working hours and/or focus blocks When ingestion runs Then all overlapping intervals in the normalized availability graph are marked unavailable with reason=ooo And any underlying working-hours or focus-block availability is suppressed within the OOO window
Holiday Deduplication Across Sources
Given the same holiday date exists both in the workspace holiday policy and in the user’s personal calendar as an all-day event When ingestion runs Then the normalized availability graph contains a single unavailable interval for that date with reason=holiday And the interval includes merged source references (e.g., policyId and eventId) And working hours on that date are not marked available within the holiday window
Time Zone Resolution and DST Transition Handling
Given a user’s working hours are defined in their local timezone and an OOO event is created in a different timezone that spans a DST transition When ingestion runs for the transition day Then the normalized availability graph reflects working-hour intervals with correct UTC offsets before and after the DST change And the OOO event’s converted UTC times align with the source event’s timezone rules without creating overlapping or missing minutes And there are no duplicate or zero-length intervals in the graph
Caching: Freshness Indicators and Stale Data Behavior
Given a successful ingestion has produced a snapshot When a client requests availability within the workspace-configured cache TTL and no source indicates newer data Then the cached snapshot is returned with freshness.fresh=true and freshness.ageMs <= TTL And when the TTL has elapsed and sources are reachable Then a refresh is triggered and the response includes the refreshed snapshot with freshness.fresh=true and updated lastSyncedAt And when sources are slow, the client can receive the previous snapshot with freshness.fresh=false and freshness.ageMs reported
Graceful Degradation on Source Unavailability
Given Google Calendar API returns 5xx errors while Microsoft 365 and workspace policies are available When ingestion runs Then data from available sources are processed and included And the last successful Google snapshot is used if available, and the response marks google.status=degraded with errorCode and lastSuccessAt And the system does not mark time as available based solely on missing Google data; it errs on the side of unavailable per workspace policy until the source recovers
Audit Logging for Availability Decisions
Given a specific user and time window are selected for investigation When audit logs are requested for that window Then the logs include: input source events and policies (with IDs), timezone conversions applied, precedence decisions taken per interval, deduplication actions, and the final reason for each interval And the logs include correlationId for the ingestion run and timestamps in ISO 8601 UTC And sensitive tokens or secrets are not logged
Explainability & Impact Preview
"As a user, I want to understand why a window was chosen so that I can confidently accept it or tweak preferences when needed."
Description

Surfaces the rationale behind each snapped window so users trust and adjust results. The UI shows per-participant local times, after-hours minutes avoided vs. alternatives, conflicts resolved, and which constraints drove the selection (e.g., holiday avoidance, focus block protection). Provides a side-by-side comparison panel for up to three candidate windows and a "Why not earlier/later?" drill-down. All explanations are derived from the same policy and availability data to ensure consistency with final outcomes and are available in both the editor and Smart Link previews.

Acceptance Criteria
Editor: Rationale panel for snapped window shows drivers and impact
Given a snapped window is selected in the editor and at least one alternative exists in the current suggestion set When the user opens the Rationale panel Then the panel displays for the selected window: - Each participant’s local start and end time with timezone abbreviation - Each participant’s after-hours minutes within the window - A list of constraint drivers that influenced the snap (Work hours, Holidays, Focus blocks) - The number of conflicts resolved relative to the top alternative - The total after-hours minutes avoided vs the top alternative (positive integer minutes) And all metrics match the scheduling engine’s calculations for the same inputs.
Compare: Side-by-side up to three candidate windows
Given the suggestion set contains two or more candidate windows When the user opens the Comparison panel Then the user can pin up to three windows side-by-side And each column shows: organizer’s timezone start/end, total after-hours minutes (sum across participants), count of participants affected after-hours, count of holiday conflicts avoided, count of focus blocks protected, and the list of primary constraint drivers And the column with the lowest total after-hours minutes is visually indicated as optimal And removing or adding a window updates all totals and indications immediately.
Drill-down: “Why not earlier/later?” shows blocking constraints
Given a candidate window of duration D is selected When the user clicks “Why not earlier?” or “Why not later?” Then the drill-down lists the blocking constraints within the current search horizon that prevent moving the window earlier/later by maintaining duration D, including participant, constraint type (Work hours, Holiday, Focus block, Existing conflict), and time range And if a feasible earlier/later window exists within the horizon, the first feasible start time is shown with an action to apply it And if none exists, a message states “No feasible slot within search horizon,” with the top three blockers listed.
Consistency: Explanations match scheduling engine outputs
Given policy and availability inputs are loaded and the engine returns a suggestion set When explanations are generated for each candidate window Then per-participant local times, after-hours minutes, conflicts resolved, and constraint drivers exactly match the engine’s computations for those inputs And any discrepancy in minutes or counts causes the UI to show an error state and suppress stale/partial values until recomputed successfully.
Parity: Explanations available in Editor and Smart Link preview
Given a Smart Link preview is opened with the same inputs as the editor When the recipient views a selected candidate window Then the Rationale panel and the Comparison view are available in read-only mode And all values (local times, after-hours minutes, conflicts resolved, constraint drivers, and deltas vs alternatives) match the editor for the same candidate windows And any views intentionally disabled for the Smart Link show a placeholder explaining unavailability.
Freshness: Explanations update after policy or availability changes
Given the user modifies work hours, holidays, focus blocks, or participant availability When the changes are saved Then the suggestion set and all explanation metrics (local times, after-hours minutes, conflicts resolved, drivers, comparisons, and drill-down results) are recalculated using the updated data And previously displayed comparisons or rationale reflecting old data are flagged as outdated and refreshed automatically.
Manual Override, Locking & Undo
"As a scheduler, I want to manually tweak and lock parts of a suggested window so that I can accommodate exceptions without losing Smart Fill’s guidance."
Description

Allows users to override Smart Fill by manually reshaping or trimming a snapped block while preserving constraint visibility. Users can lock selected segments to prevent re-optimization, and use undo/redo to step through changes. The system marks overridden areas and recalculates impact metrics in real time so trade-offs are clear. Overrides are persisted to the scheduling artifact (event draft or link template) and honored by subsequent Smart Fill operations unless unlocked.

Acceptance Criteria
Manual Reshape Override of Smart‑Filled Block
Given a Smart Fill–generated block exists on the timeline and the user has edit permissions When the user drags a block edge or control point to reshape or trim, including into after‑hours or across focus/holiday regions Then the reshape is allowed without auto‑snapping back And the overridden portion is visually marked as Overridden per design spec And constraint regions (work hours, holidays, focus blocks) remain visible during the interaction And impact metrics (after‑hours minutes, affected participants) update within 300 ms of pointer release And the final shape respects the event’s min/max duration constraints And the change is persisted to the artifact via autosave or explicit save
Segment Locking Prevents Re‑Optimization
Given a Smart‑filled block is selected When the user locks the start edge, end edge, or a specified internal segment Then a lock indicator appears on each locked edge/segment And subsequent Smart Fill operations do not alter any timestamps within locked segments And manual resize/drag on locked edges/segments is blocked with a lock feedback state And unlocking re‑enables both Smart Fill and manual edits for those segments And lock states persist to the artifact and across reloads
Undo/Redo for Overrides and Locks
Given an open scheduling artifact with Smart Fill enabled When the user performs actions including reshape, trim, lock/unlock, and Smart Fill re‑apply Then pressing Ctrl/Cmd+Z undoes the last action step‑by‑step up to 50 actions And pressing Ctrl/Cmd+Shift+Z (or Ctrl/Cmd+Y) redoes undone actions in order And each undo/redo restores geometry, lock states, override markers, and impact metrics consistently And each undo/redo executes within 150 ms And the undo stack is scoped to the current artifact and clears on artifact close
Visual Marking and Real‑Time Impact Metrics
Given a block contains any manually overridden portion When the timeline is rendered or the block geometry changes Then overridden areas are visually distinguished using the designated pattern and accessible label "Overridden" And the impact panel displays total after‑hours minutes, number of affected participants, and count of holiday/focus conflicts And metrics recompute after each geometry change and Smart Fill invocation, reflecting the latest state within 300 ms And client‑side and server‑side metrics agree within ±1 minute per participant
Persistence and Smart Fill Honor of Overrides
Given an artifact (event draft or link template) contains overrides and/or locks When the artifact is saved and reopened, or a template is used to generate a link Then overridden shapes and lock states are preserved exactly And running Smart Fill again does not modify any locked or manually overridden segments unless explicitly unlocked or reset by the user And if honoring overrides prevents a feasible solution, the system surfaces a non‑blocking warning and suggested alternatives without changing honored segments
Constraint Visibility During Manual Editing
Given constraint layers for work hours, holidays, and focus blocks are enabled When the user drags or resizes a block to override Smart Fill Then all constraint layers remain visible and unobscured by the block fill And color/contrast maintains at least a 3:1 ratio between block and constraint overlays for accessibility And constraint legends/tooltips remain available during the interaction

Snapshot Compare

Save multiple canvas scenarios (A/B/C) and compare coverage, after‑hours minutes, legal risk, and fairness impact side by side. Choose the best plan with clear metrics, then publish it to links or series with one click.

Requirements

Multi-Snapshot Save & Versioning
"As a remote team lead, I want to save multiple scenario snapshots of our meeting plan so that I can explore alternatives without losing my current setup."
Description

Enable users to save multiple named snapshots of the scheduling canvas (e.g., A/B/C) with immutable state, including team lineup, time zone assumptions, work hours, holiday calendars, focus blocks, and selected constraints. Provide cloning to iterate from an existing snapshot, tagging for organization, and automatic versioning with creator, timestamp, and notes. Ensure snapshots capture all dependent configuration so results remain consistent despite subsequent global setting changes, and support deduplication and storage quotas. Enforce permissions so only authorized editors can create, update notes, clone, or archive snapshots, while viewers can access read-only details. Handle DST boundaries and future holiday updates by persisting effective calendars at snapshot time. Provide reliable persistence, quick retrieval, and conflict-safe operations.

Acceptance Criteria
Immutable Snapshot Save with Calendar Persistence
Given an editor with an active canvas containing a defined team lineup, time zone assumptions, work hours, holiday calendars, focus blocks, and selected constraints When they click Save Snapshot, provide a unique name, and confirm Then the system persists an immutable snapshot that includes the full captured state listed above, including effective holiday calendars and DST‑adjusted schedules for the snapshot’s time horizon And subsequent changes to any global/team settings do not alter the snapshot data or its computed metrics And the snapshot is assigned a unique ID and a content hash of the captured state and is stored atomically And the operation succeeds with P95 save latency ≤ 1.5s and returns success with the snapshot ID
Clone Snapshot and Inheritance
Given an existing snapshot A and editor permission When the user selects Clone on A and enters a new name Then the system creates snapshot B whose captured state equals A’s state and whose lineage metadata points to A And B receives a new unique ID, an incremented version within the lineage, creator identity, and UTC timestamp And editing B does not modify A And the clone operation deduplicates shared payloads to avoid double‑counting storage
Automatic Versioning and Notes
Given a lineage of snapshots derived from the same base When a new snapshot is saved or cloned in that lineage Then the system assigns a monotonically increasing version number and records creator identity, UTC timestamp, and optional notes (0–2,000 chars) And updating notes on an existing snapshot creates an audit log entry with editor, timestamp, and previous value And editing notes does not change the version number or captured state
Tagging and Organization
Given an editor on a snapshot When they add, remove, or rename tags on the snapshot Then tags are case‑insensitively normalized, deduplicated, and limited to 20 tags per snapshot and 40 characters per tag And snapshots can be filtered by one or more tags, returning correct results within P95 300ms for up to 1,000 snapshots And viewers can read tags but cannot modify them
Permissions and Access Control
Given a user with Viewer role When they attempt to create, clone, archive, or update notes/tags on a snapshot Then the action is blocked with an authorization error and no changes are persisted And users with Editor role can perform create, clone, update notes/tags, and archive actions successfully And archived snapshots remain readable but not editable by any role
Deduplication and Storage Quotas
Given a workspace with a storage quota of Q MB and an existing snapshot payload P When an editor saves a new snapshot whose captured state is byte‑for‑byte identical to P Then the system stores a single payload copy referenced by multiple snapshots and counts its size toward quota once And when a save would exceed the quota, the system rejects the save with a clear message that includes current usage, quota, and estimated size And the usage dashboard reflects storage changes within 1 minute
Operational Reliability: Retrieval Performance and Concurrency Controls
Given multiple clients concurrently fetching and editing snapshot metadata When two editors attempt to update the same snapshot metadata simultaneously Then the system enforces conflict‑safe updates using ETag/If‑Match (or equivalent) and returns a 409 Conflict to the second writer without data loss And all read operations (fetch by ID; list with filters and pagination of 50 per page) return within P95 300ms for up to 1,000 snapshots And all write operations are idempotent when retried with the same idempotency key for 60 seconds
Comparison Metrics Engine
"As a planner, I want consistent, trustworthy metrics for each scenario so that I can compare options objectively and explain trade-offs to stakeholders."
Description

Compute standardized metrics for each snapshot and their deltas side by side, including total team coverage window, participant-specific after-hours minutes, conflict counts with focus blocks, holiday conflicts, legal/policy risk score based on organization rules, and fairness impact indices (e.g., distribution of off-hour burden across time zones). Support configurable weights and thresholds, baseline selection for delta calculations, and normalization across team size and role criticality. Accurately account for DST changes, regional holidays, and individual work-hour exceptions. Provide caching and incremental recomputation to keep comparisons fast on large teams, with clear definitions and tooltips for each metric. Expose a stable API to the UI, include validation, and record metric computation versions for auditability.

Acceptance Criteria
Compute Standardized Metrics Per Snapshot
Given a snapshot with team members, work hours, focus blocks, regional holidays, and organization rule set loaded When the metrics engine computes metrics for the snapshot Then the response includes, per snapshot: totalCoverageMinutes, perParticipant.afterHoursMinutes, perParticipant.focusBlockConflictCount, perParticipant.holidayConflictCount, risk.legalPolicy.score, fairness.offHourBurden.giniCoefficient, fairness.offHourBurden.maxMinDiffMinutes And all minute-based metrics are integers in minutes and non-negative And perParticipant arrays include all participants present in the snapshot, matching by participantId And computations are deterministic: identical input yields identical outputs And unit tests with a fixed fixture produce expected values within ±0 minutes (exact match after integer rounding)
Configurable Weights, Thresholds, and Normalization
Given a metricsConfig containing weights for risk and fairness components, threshold values for warnings, and a normalization toggle with roleCriticality weights When the engine computes metrics with this config Then risk.legalPolicy.score equals the weighted sum of configured sub-scores within ±0.01 And fairness indices are computed with roleCriticality weighting when normalization is enabled and without it when disabled And team-size–normalized metrics (e.g., per-capita afterHoursMinutes) are present when normalization is enabled And when weights do not sum to 1.0 the engine normalizes them and returns normalizedWeights in the payload And when no config is provided the engine applies the organization default config and flags defaultConfigApplied=true And if any threshold is breached the response includes a thresholdsBreached array with metricId, value, threshold, and direction
Baseline Selection and Delta Computation
Given two or more snapshots A/B/C and a baselineSnapshotId passed as A When metrics are requested for [A,B,C] Then each non-derived metric includes a delta vs baseline with fields {value, percent, direction} And deltas for A vs A are zero with direction="neutral" And delta signs are positive when the metric increases vs baseline and negative when it decreases And percent deltas are computed as (candidate - baseline)/abs(baseline) and are null when baseline=0 And omitting baselineSnapshotId defaults the baseline to the first snapshot in the request order And the API returns baselineSnapshotId in the response metadata
DST, Holidays, and Work-Hour Exceptions Handling
Given participants across regions where a snapshot schedule spans a DST transition in any locale When afterHoursMinutes and coverage windows are computed Then local times are resolved using IANA time zones with correct UTC offsets per date, including DST shifts And afterHoursMinutes are counted relative to each participant’s local declared work hours on each date And holiday conflicts include regional public holidays from the assigned calendar and individual overrides And individual work-hour exceptions (e.g., temporary shifts) override default hours for the specified dates And unit tests include cases where meetings straddle DST boundaries and results match expected minutes within ±1 minute due to boundary rounding
Caching and Incremental Recomputation Performance
Given a team of 500 participants and 5 snapshots with overlapping schedules When metrics are requested repeatedly with identical inputs Then the engine serves results from cache with a cacheHitRate ≥ 0.8 over 100 repeated requests And P95 end-to-end latency per request is ≤ 2000 ms on reference hardware for cached responses When only one participant’s availability changes Then incremental recomputation updates only affected metrics and P95 latency is ≤ 500 ms And cache keys include orgId, teamId, snapshotIds, metricsConfig hash, calendars version, and computationVersion And cache invalidates on any change to inputs, calendars, rules, or computationVersion
Stable API with Validation and Versioned Audit Trail
Given the UI calls the metrics API at /v1/metrics/compare with a well-formed request When the request is processed Then the response conforms to the published JSON schema and includes computationVersion and rulesetVersion And requests failing schema validation receive HTTP 400 with machine-readable error codes and fields And breaking changes are only introduced with a new major version path (/v2) while /v1 remains backward compatible And each computation is logged with a unique auditId, timestamp, input hashes, requesting userId/serviceId, and versions And re-running the same request reproduces the same auditId deterministically when idempotencyKey is supplied
Metric Definitions and Tooltips Exposure to UI
Given the UI requests metric definitions from /v1/metrics/definitions with a computationVersion When the definitions are returned Then each metric includes id, name, description, formula summary, units, directionality (goodUp or goodDown), and exampleRanges And definitions include clear attribution of normalization and weighting where applicable And the response is localized when an Accept-Language header is provided, defaulting to en-US And the computationVersion in the definitions matches that in metrics responses, or the API returns HTTP 409 on mismatch
Side-by-Side Compare UI
"As a user comparing scenarios, I want a clear side-by-side view with highlights and sorting so that I can quickly identify the best plan."
Description

Present up to N snapshots in a responsive, accessible grid with metric cards that align by category and highlight best values and negative trade-offs. Allow sorting by any metric, toggling between absolute values and deltas from a chosen baseline, and expanding a snapshot to see participant-level breakdowns. Provide color-coded indicators, tooltips with metric definitions, and quick filters (e.g., hide snapshots exceeding risk thresholds). Enable renaming, tagging, and notes inline, and preserve the compare view state in the URL for easy sharing. Ensure keyboard navigation, screen-reader labels, and performant rendering for large metric sets.

Acceptance Criteria
Responsive Compare Grid (Up to N Snapshots) with Category Alignment
Given up to 12 snapshots are selected and the viewport widths are 320px, 768px, 1024px, and 1440px, When the compare view renders, Then the grid displays 1, 2, 3, and 4 columns respectively and supports horizontal scroll when columns exceed the viewport. Given multiple metric categories exist, When the grid renders, Then metric cards align row-by-row by category and metric key across snapshots with consistent height and order. Given a dataset of 10 snapshots each with 150 metrics, When loading on a reference device, Then first contentful paint is under 1200ms and vertical/horizontal scrolling maintains at least 50 FPS with no main-thread frame over 50ms. Given keyboard-only navigation, When tabbing and using arrow keys in the grid, Then focus moves cell-by-cell in reading order, lands on all interactive controls, has a visible focus indicator, and never traps the user.
Metric Cards: Best/Trade-off Indicators and Tooltips
Given all snapshots for a metric are visible, When values are evaluated, Then the best value per metric is highlighted and ties are indicated uniformly across tied snapshots. Given metric directionality (higher-is-better or lower-is-better), When computing best and trade-offs, Then the configured rule per metric is honored and negative trade-offs are flagged distinctly from neutral/best. Given the UI theme, When rendering indicators, Then color contrast meets WCAG 2.1 AA (text 4.5:1, graphical indicators 3:1) and a legend or label clarifies color meaning. Given a user hovers or focuses a metric info icon, When a tooltip appears, Then it shows the metric definition, unit, directionality, and calculation notes, is reachable by keyboard, announced by a screen reader via aria-describedby, and remains open while hovered/focused.
Sort Snapshots by Any Metric
Given a metric header or card action menu, When the user selects Sort Ascending or Sort Descending, Then snapshot columns reorder accordingly within 300ms and the active sort field and direction are visibly indicated. Given equal values for the sort metric, When sorting, Then a stable tie-breaker by snapshot name ascending is applied. Given a sort is active, When the page URL is copied and opened in a new session, Then the same sort field and direction are restored from the URL state.
Absolute vs Delta Toggle with Baseline Selection
Given multiple snapshots are displayed, When a baseline is selected, Then exactly one baseline is active, visually marked, and the selection is encoded in the URL and restored on reload. Given the Absolute/Delta toggle, When switching modes, Then all metric values update within 200ms; deltas render signed values with units, apply color coding for improvement/regression per metric rule, and the baseline displays neutral formatting. Given the active baseline becomes hidden by a filter, When filters apply, Then the user is prompted to choose a new baseline or the system auto-selects the first visible snapshot and announces the change without losing sort or filter state.
Quick Filters and Risk Threshold Hiding
Given risk threshold controls are available, When a user sets a threshold for a metric (e.g., legal risk > 0%), Then any snapshot exceeding that threshold is hidden from the grid immediately. Given filters are applied, When the grid updates, Then a visible/total counter reflects the result and, if zero remain, an empty state with a clear Reset Filters action is shown. Given filters are applied, When the compare view URL is shared and later opened, Then all filter parameters persist and the same filtered set is restored.
Expand Snapshot to Participant-level Breakdown
Given a snapshot column has an Expand control, When the user activates it, Then a panel opens showing participant-level metrics (coverage, after-hours minutes, fairness impact) with search and pagination if more than 50 participants. Given a large snapshot of 200 participants, When expanding, Then rows render with virtualization so that input latency during scroll remains under 100ms and memory usage does not grow unbounded. Given a screen-reader user, When the panel opens, Then focus moves to the panel header, the panel is labeled with the snapshot name and purpose, and pressing Escape closes it and returns focus to the originating control.
Inline Rename, Tag, and Notes
Given the user has edit permissions, When renaming a snapshot inline, Then the name accepts 1–60 characters, prevents duplicates within the compare set, validates on input, and saves on Enter or blur with optimistic UI and error rollback on failure. Given a tags input for the snapshot, When adding or removing tags, Then up to 20 unique tags (max 24 characters each) are supported, tags render as chips immediately, and changes persist to storage. Given a notes field is present, When the user enters up to 2000 characters, Then it autosaves within 1 second of idle and, when the compare URL is shared, the selected snapshot set and view state persist while metadata changes (name/tags/notes) persist in backend.
One-Click Publish & Sync
"As a meeting owner, I want to publish the best scenario directly from the comparison so that I can finalize the plan without reconfiguring details."
Description

Allow selection of a winning snapshot from the compare view and publish it with one click to Smart Links or recurring series, syncing to connected calendars (e.g., Google Workspace, Microsoft 365) while honoring work hours, focus blocks, and holiday constraints. Perform pre-publish validation for conflicts and policy thresholds, present a concise confirmation summary, and provide automatic notifications to participants. Support rollback to the prior plan, idempotent retries, and audit logging of publish events. Update any existing shared links to point to the new plan while preserving historical access to archived snapshots.

Acceptance Criteria
One-Click Publish from Compare View
Given I am in the Snapshot Compare view with at least two saved snapshots and have selected one as the winner And I have publish permission for the workspace When I click the Publish button Then the system disables the Publish button and shows a confirmation dialog within 500 ms And the selected snapshot ID and target destinations are carried into the confirmation step
Pre-Publish Validation of Conflicts and Policy Thresholds
Given a selected snapshot and organization policy thresholds for after-hours, legal risk, and fairness impact And connected calendars with work hours, focus blocks, and holidays configured When pre-publish validation runs Then hard violations block publish with a labeled list of violating events and policy codes And soft warnings are displayed but allow continue And the validation completes within 2 seconds for up to 200 events And total counts for conflicts, minutes outside policy, and affected participants are displayed
Publish to Smart Links and Recurring Series with Calendar Sync
Given I confirm publishing to one or more Smart Links and/or a recurring series synced to Google Workspace or Microsoft 365 When publish executes Then existing Smart Links atomically point to the new plan version only upon successful completion And recurring series and instances are created/updated in connected calendars honoring work hours, focus blocks, and holidays (no events created outside constraints) And time zones are correctly applied so local times match the snapshot schedule And the operation completes within 60 seconds for up to 200 events with a success rate of 99% or better And on partial failure, Smart Links and series remain on the prior plan
Confirmation Summary with Metrics and Impact
Given the confirmation dialog is shown before publishing When displayed Then it includes: coverage percentage, total after-hours minutes, legal risk rating, fairness impact score, number of affected participants, counts of events to create/update/cancel, and any policy warnings And values match the latest validation results within +/-0.1% And the user can Proceed or Cancel; Cancel closes the dialog and re-enables Publish without side effects
Automatic Participant Notifications
Given publish succeeds When notifications are sent Then each participant receives a single notification per changed series/link within 5 minutes, localized to their time zone with a clear summary of changes And recipients opted out of emails are skipped for email but still receive calendar updates And no duplicate notifications are sent for idempotent retries And bounces or delivery failures are logged with reasons in the audit log
Idempotent Retries and Failure Handling
Given a transient failure occurs during publish and a retry is triggered with the same idempotency key within 15 minutes When the retry runs Then no duplicate calendar events or notifications are produced And the final state is equivalent to a single successful publish And the user sees a single publish record in history with combined telemetry And operations are retried with exponential backoff up to 3 times before surfacing an error
Rollback, Link Preservation, and Audit Logging
Given a publish completed and a prior plan exists When the user initiates Rollback within 24 hours Then Smart Links atomically repoint to the prior plan and the current plan is archived And connected calendars are reverted to match the prior plan, honoring work hours, focus blocks, and holidays And participants are notified of the rollback within 10 minutes And archived snapshots remain accessible via versioned, read-only URLs for historical access And an audit log entry captures actor, timestamps, snapshot IDs, metrics before/after, affected links/series, external calendar change IDs, and outcome
Risk & Fairness Weighting Configuration
"As an admin, I want to set risk and fairness weights and thresholds so that comparisons reflect our company policies and compliance requirements."
Description

Provide organization-level controls to configure weights and thresholds for legal/policy risk and fairness metrics used in comparisons. Offer recommended presets by region and company size, allow team-level overrides with permissions, and show real-time impact of changes on computed scores in the compare view. Version and timestamp policy configurations and associate them to snapshots for reproducibility. Support import/export of policy settings and guardrails to prevent publishing snapshots that exceed mandatory thresholds.

Acceptance Criteria
Org-Level Policy Weights and Thresholds Configuration
Given I am an Organization Admin on the Policy Settings page When I set Legal/Policy Risk weight to 60% and Fairness weight to 40% and define mandatory thresholds for each metric and click Save Then the weights must sum to 100% or a validation error specifies the delta and Save remains disabled And values outside allowed ranges are flagged inline and are not saved And on successful save, the configuration persists at the organization scope and becomes the default for teams without overrides within 10 seconds And the compare scoring uses the saved weights on next compute and the active policy version label updates
Apply Recommended Preset by Region and Company Size
Given I select a recommended preset (e.g., "EU · Mid-size (250–999)") in Policy Settings When I click Apply Then weight and threshold fields populate with the preset values without immediately saving And a preview diff highlights changes vs current values And Cancel reverts all fields to their prior values; Save commits the preset values And after Save, the preset name and preset version are recorded in policy metadata
Team-Level Overrides with Permission Controls
Given a user without team override permission views Team Policy Settings When they attempt to edit any policy field Then fields are read-only, marked "Inherited from Org", and Save is unavailable Given a user with team override permission views Team Policy Settings When they enable "Override org policy", change values, and click Save Then a team-scoped policy version is created, used for that team’s comparisons, and an "Revert to Inherit" control appears And API attempts by unauthorized identities to modify policy return HTTP 403 and are audit-logged
Real-Time Compare Recalculation on Policy Change
Given the Compare view is open with multiple snapshots loaded and the policy panel visible When I adjust the Risk/Fairness weights or thresholds in the panel (without saving) Then all displayed composite scores and ranking positions recalculate within 300 ms without a page reload And changes are clearly marked as Draft with an option to Reset to Saved And toggling between Draft and Saved shows the delta per snapshot for coverage, after-hours minutes, legal risk, and fairness
Versioning and Snapshot Association for Reproducibility
Given I save changes to policy settings Then a new immutable policy version is created with incremented version ID, author, UTC timestamp, and change summary And any new snapshot created stores a reference to the active policy version Given I open an existing snapshot created under a prior policy version When I view it in Compare Then the historical policy version is resolved and applied so results match creation time, and the UI shows "Policy vX.Y (timestamp)" with a link to view differences from current
Import and Export of Policy Settings
Given I click Export on Policy Settings Then a JSON file downloads containing schemaVersion, scope (org/team), weights, thresholds, preset metadata, and version info, with no secrets included Given I choose Import and upload a policy JSON When validation passes in dry-run Then a preview of incoming changes is shown and, upon confirm, a new policy version is created with source=import When validation fails Then import is blocked and error messages specify invalid fields with JSON pointer paths
Guardrails Blocking Publish Above Mandatory Thresholds
Given a snapshot’s computed metrics violate one or more mandatory thresholds in the active policy When a user attempts to publish the snapshot to links or series Then the Publish action is disabled with a message listing violated thresholds and metrics And API publish requests are rejected with HTTP 422 and machine-readable error codes And an audit log entry records user, snapshot ID, violated rules, and timestamp
Approvals, Audit & Permissions
"As a compliance-conscious manager, I want approvals and audit trails around publishing plans so that we meet governance standards and can trace decisions."
Description

Introduce role-based permissions for creating, comparing, and publishing snapshots, with optional approval workflows when risk thresholds are exceeded or cross-team schedules are impacted. Maintain an audit trail capturing snapshot creation, edits, metric versions, comparisons, approvals, and publish actions with actor, timestamp, and changes. Provide exportable logs and retention controls aligned with organizational policies. Ensure sensitive participant-level metrics are masked for viewers without appropriate access.

Acceptance Criteria
RBAC: Create, Compare, Publish Snapshot Permissions
Given a workspace with roles Owner, Editor, Approver, Viewer, When a user attempts to create a snapshot, Then only Owner or Editor can create; Viewer and Approver receive 403 and the "Save Snapshot" control is disabled. Given any snapshot exists, When a user without compare permission opens Snapshot Compare, Then the compare UI is hidden and API calls to start comparisons return 403 with error code RBAC_001. Given any snapshot exists, When a user with publish permission initiates publish, Then publish succeeds and is logged; When a user without publish permission initiates publish, Then a 403 is returned and no state changes. Given an Owner updates a user’s permissions, When the user retries the action, Then the new permission takes effect within 60 seconds without requiring logout. Given a shared smart link is accessed by a Link Viewer, When restricted actions are attempted, Then permissions map to Viewer and the same restrictions apply.
Approval Workflow: Risk Threshold Exceeded
Given approval workflow is enabled and risk thresholds are configured (e.g., After-Hours Minutes > 30 OR Legal Risk ≥ Medium), When publish is initiated for a snapshot exceeding any threshold, Then publish is paused and an approval request is created with status Pending and approver list populated. Given an approval request is Pending, When an Approver selects Approve, Then the snapshot publishes within 10 seconds and the decision (Approve, actor, timestamp, rationale) is recorded in the audit log. Given an approval request is Pending, When an Approver selects Reject and enters a reason, Then publish remains blocked, requester is notified, and the rejection reason is stored and visible. Given an SLA (e.g., 48h) is configured, When no approver acts within the SLA, Then the request escalates to Org Approvers and the escalation is logged. Given approval workflow is disabled, When publish is initiated, Then no approval is required regardless of risk and publish proceeds.
Audit Trail: Snapshot Lifecycle Logging
Given actions occur (snapshot create, snapshot edit, compare run, metric version selection, approval requested, approval decision, publish, retention change, export downloaded), When each action completes, Then an immutable audit entry is created. Then each audit entry contains: unique ID, action type, actor ID and role, entity ID, ISO‑8601 UTC timestamp, before/after summary or diff hash, IP/device fingerprint, and correlation ID. Then audit entries are append-only; no role can modify or delete entries; attempts to modify are denied with 403 and are themselves logged. Given filters (time range, actor, action type, entity), When a query is executed for up to 10,000 records, Then results return within 2 seconds. Given a user views an entry, When timestamps are displayed, Then local time with UTC offset is shown with exact UTC available on hover or details.
Log Export & Retention Controls
Given an Owner or Auditor requests an audit export with filters, When export is generated, Then a downloadable file in the selected format (CSV or JSON) with headers and all matching records is provided. Then the export includes a SHA‑256 checksum and metadata (exported_by, exported_at UTC, filter summary) as embedded fields or a sidecar .sha256 file. Given an export exceeds 100,000 records, When requested, Then the system streams or chunks the export without timeout and completes within 10 minutes or provides a resumable link. Given retention is set to N days (e.g., 365), When an entry’s age exceeds N days and is not under legal hold, Then it is purged automatically and a purge event is logged. Given a legal hold is applied to entities or time ranges, When retention jobs run, Then entries under hold are not purged until the hold is released and the release is logged.
Data Masking: Participant-Level Metrics for Unauthorized Viewers
Given a user lacks the "View Sensitive Metrics" permission, When viewing snapshots or comparisons, Then participant PII (names, emails, timezones) and per-person metrics are masked or omitted while aggregates remain visible. Then API responses for unauthorized users exclude or null sensitive fields and include masked=true flags; direct calls to sensitive endpoints return 403 with error code DATA_MASK_001. Given an unauthorized user exports data that could include participant-level fields, When export is generated, Then sensitive columns are masked/omitted consistently with UI policies. Given a user has the "View Sensitive Metrics" permission, When viewing the same content, Then full participant details and per-person metrics are visible. Then all access attempts (granted or denied) to sensitive data are recorded in the audit log.
Cross-Team Impact: Publish Requires Multi-Team Approval
Given a snapshot impacts schedules across multiple teams (as determined by participants’ team assignments and affected series), When publish is initiated, Then the system enumerates impacted teams and requires approval from at least one Approver per impacted team. Then the approval panel lists required approvers by team with statuses (Pending/Approved/Rejected) and publish is blocked until all required approvals are Approved. Given any impacted team Rejects, When rejection is submitted, Then publish remains blocked, the requester sees rejection reasons, and override is only available to Org Owner with explicit "Bypass Multi-Team Approval" permission, creating a high-severity audit entry. Given an impacted team has no assigned Approver, When publish is initiated, Then routing falls back to the Team Lead or Org Approvers and this routing is displayed and logged.
Metric Versioning: Reproducible Comparisons
Given a comparison is saved, When metrics are computed, Then the metric engine version and configuration (formula IDs, weights, thresholds) are stored with the comparison/snapshot. Given the metric engine is upgraded later, When a user selects "Reproduce with saved version" on a past comparison, Then the displayed metric values exactly match the original saved values. Given a user selects "Recompute with latest version", When recomputation completes, Then the run is labeled "Latest" with the new version and both results can be viewed side by side with distinct version tags. Then the audit log for comparisons includes the metric engine version and a hash of the configuration used for each run. Given a comparison export is generated, When downloaded, Then version identifiers and configuration hashes are included to support external reproducibility.
Shareable Compare Reports & Export
"As a stakeholder, I want a shareable summary of the comparison so that I can review and provide feedback without needing edit access."
Description

Generate read-only compare reports that can be shared via expiring links and exported to PDF or CSV for offline review. Include key metrics, deltas, visual highlights, assumptions, and snapshot notes, with options to redact sensitive fields. Support embedding a live compare widget in internal wikis, preserve accessibility in exports, and watermark documents with snapshot IDs and timestamps for traceability.

Acceptance Criteria
Generate Expiring Read-Only Share Link
Given a published Snapshot Compare report with snapshots A, B, and C When I create a share link with an expiration set to 48 hours and access level set to "Anyone with the link" Then opening the link renders a read-only compare view with no edit or publish controls And any attempt to modify the report via UI or API returns HTTP 403 And the link returns HTTP 410 with an expiration message immediately after the 48-hour window And the link can be manually revoked, after which it returns HTTP 410 within 60 seconds And the share page sends an X-Robots-Tag: noindex header
PDF Export Includes Required Content and Traceability
Given a published compare report with visual highlights, key metrics, deltas, assumptions, and snapshot notes When I export to PDF Then the PDF contains all snapshots side by side with labeled metrics and computed deltas And visual highlights (e.g., after-hours minutes in red) are preserved with a legend And the PDF footer on every page includes Report ID, all Snapshot IDs, and an ISO 8601 generated_at timestamp And page breaks do not truncate tables or charts And the PDF opens without errors in Adobe Acrobat and Chrome's PDF viewer
CSV Export Structure and Completeness
Given a published compare report with metrics and deltas for snapshots A, B, and C When I export to CSV Then the CSV includes one header row and one data row per snapshot And columns include: report_id, generated_at, snapshot_id, snapshot_name, metric_*, delta_*, assumptions, snapshot_notes And numeric fields are unformatted numbers (no thousands separators, period as decimal) And text fields are UTF-8 encoded and quoted when containing commas or newlines And generated_at is in ISO 8601 with timezone (e.g., 2025-09-01T12:00:00Z)
Redaction Options Applied Across Views and Exports
Given I select to redact fields "participant_emails" and "legal_risk_notes" for a compare report When I generate a share link and export PDF and CSV Then those fields are removed or replaced with the string "REDACTED" consistently across the share view, PDF, and CSV And no redacted values are present in the DOM, network responses, or file metadata And the share view displays a visible "Redactions applied" badge with a tooltip listing redacted fields And turning redaction off and regenerating outputs restores the original values
Embeddable Live Compare Widget for Internal Wikis
Given I copy the embed code for a compare report When I paste it into Confluence, Notion, and GitHub Wiki pages Then the widget renders the read-only compare view within the host page and respects the report's redaction and expiration rules And the widget is responsive between 320 px and 1200 px widths without horizontal scroll And if embedding is blocked by the host, a fallback static preview image with a link to the share URL is displayed And refreshing the host page shows the latest published version of the report
Accessibility Compliance for Share View and Exports
Given a compare report with charts, tables, and color-coded deltas When accessed via the share view or exported to PDF/CSV Then the share view meets WCAG 2.1 AA for keyboard navigation, focus order, ARIA labels, and color contrast And charts include programmatic alt text/ARIA descriptions; color is not the sole means of conveying deltas And the PDF is a tagged PDF with correct reading order, headings, table structure, and alt text for charts And the CSV includes descriptive headers; no critical information is conveyed by color only
Watermark Presence and Consistency Across Outputs
Given a compare report with snapshots A, B, and C When I view the share page, export to PDF, and export to CSV Then the share page footer displays Report ID and generated_at timestamp And the PDF footer on every page includes Report ID and generated_at; the CSV includes columns for report_id, generated_at, and snapshot_id per row And timestamps use ISO 8601 with timezone And the identifiers match the values in the system audit log

Guest Overlay

Drop in a candidate, customer, or partner by email or time zone to preview their hours and holidays on the heatmap—without changing your team setup. Generate an ephemeral share to validate choices before sending a formal invite.

Requirements

Quick Guest Add by Email/Timezone
"As a remote team lead, I want to quickly preview a guest’s scheduling context by entering their email or time zone so that I can choose sane cross-region windows without reconfiguring my team."
Description

Allow users to add an external participant by entering an email address or selecting/providing an IANA time zone. Validate inputs, resolve the guest’s local time zone when explicitly provided, and default to a selectable region if only partial information is available. The guest is treated as a transient session entity and is not persisted to the team roster or calendar integrations. Provide clear UI affordances to add, replace, or remove the guest with immediate feedback. This enables fast, low-friction previews of a guest’s availability context without altering team settings, supporting the "drop-in" workflow central to Guest Overlay.

Acceptance Criteria
Add Guest via Valid IANA Time Zone
Given the Add Guest control is visible and no guest is currently applied When the user enters a valid IANA time zone (e.g., "America/New_York") and submits Then a transient guest entity is created in the current session only And the heatmap overlay updates to that time zone within 500 ms And the guest’s local work hours, holidays, and focus blocks render according to the selected time zone And no data about the guest is written to team roster or any calendar integration
Add Guest via Email With Time Zone Selection
Given the Add Guest input accepts an email and no time zone has been specified When the user enters a syntactically valid email address and submits Then the system prompts the user to select a time zone from a searchable list with a default region pre-selected And the user must explicitly pick a time zone before applying When the user confirms the selected time zone Then the guest overlay is applied within 500 ms and reflects the chosen time zone And the guest is not persisted to roster or external calendars And if the user cancels the picker, no guest is added and the base view remains unchanged
Partial or Ambiguous Time Zone Input Handling
Given the user enters a partial or ambiguous time zone string (e.g., "Pacific" or "EST") When the user submits the input Then the system displays a disambiguation selector with candidate IANA time zones and a default region pre-selected And the Add/Apply action remains disabled until a specific IANA time zone is selected And selecting a specific IANA time zone applies the guest overlay within 500 ms And entering an invalid token that matches no IANA time zone shows an inline error and prevents submission
Replace Existing Guest
Given a guest overlay is currently active When the user adds another guest via email-plus-time-zone or direct IANA time zone entry Then the existing guest overlay is replaced with the new guest’s overlay And the timeline zoom level and current view range remain unchanged And visibility toggles (work hours, holidays, focus blocks) retain their current states And replacement completes within 500 ms of confirmation
Remove Guest Overlay
Given a guest overlay is currently active When the user clicks Remove Guest Then the guest overlay is cleared within 300 ms And base team overlays and settings remain unchanged And the input controls reset to their default empty state And no guest data is persisted anywhere
Validation and Error Feedback
Given the Add Guest form is visible When the user enters an invalid email format Then an inline, accessible error message is displayed within 100 ms and submission is blocked When the user enters an invalid IANA time zone string Then an inline, accessible error message is displayed within 100 ms and submission is blocked And clearing or correcting the input removes the error state and enables submission And all errors are announced via ARIA live region for screen readers
Session Scope and Non-Persistence
Given a guest overlay has been applied When the user reloads the page or starts a new session Then no guest overlay is automatically restored And the guest is absent from the team roster And no entries have been created or modified in linked calendar integrations And analytics/events, if any, do not include personally identifiable guest details beyond transient session identifiers
Guest Heatmap Overlay
"As a scheduler, I want to see a guest’s hours overlaid on our team heatmap so that I can immediately spot mutually acceptable meeting windows."
Description

Render the guest’s working hours, off-hours, and holidays as an overlay on the existing team heatmap using distinct colors, patterns, and legend labels. Harmonize scales and time axes so that team members and the guest are aligned visually. Provide hover tooltips with local-time rollovers for all parties and keyboard-accessible focus states. Respect existing Timeglue rules (work hours, focus blocks) for the team while applying the guest’s constraints in parallel. Ensure smooth performance for timeline drag, zoom, and paint operations with the overlay active. The result is an at-a-glance visualization that reveals mutual sane windows without modifying team configuration.

Acceptance Criteria
Distinct Overlay Rendering & Legend
Given a team heatmap is visible and a guest is added by valid email or time zone When the guest overlay is rendered Then the guest’s working hours, off-hours, and holidays are drawn with visual styles that are not used by any team layer And the legend shows three entries labeled "Guest Working", "Guest Off-Hours", and "Guest Holiday" And each legend entry includes both a color and a unique pattern swatch And legend text contrast is >= 4.5:1 against its background And guest holidays are sourced from the calendar associated with the guest’s time zone/region and appear only on matching dates And turning the overlay off returns the heatmap to its prior state with no visual remnants
Aligned Time Scales and Axes
Given the team heatmap’s time axis, gridlines, and cell granularity are configured When the guest overlay is activated Then overlay cells align 1:1 with team cells by UTC timestamp at all zoom levels And axis tick marks and labels remain unchanged And there is no horizontal drift exceeding 0.5px across the visible range at 100% zoom And zooming in/out maintains alignment without reflowing existing team cells
Local-Time Hover Tooltips
Given the pointer hovers or keyboard focus lands on a time cell with the guest overlay active When the tooltip is shown Then it displays the slot’s date and time in the local time for the guest and each team member And the tooltip appears within 150 ms on hover and within 100 ms on keyboard focus And the tooltip is dismissible via Esc and on mouseout/focusout And the tooltip remains within the viewport without clipping
Keyboard Accessibility and Focus Navigation
Given a user navigates the heatmap using only the keyboard When the heatmap receives focus Then cells are navigable with Arrow keys (1 cell), PageUp/PageDown (1 day), and Home/End (start/end of view) And Enter/Space opens the tooltip for the focused cell And the visible focus indicator has a contrast ratio >= 3:1 against adjacent colors And the overlay and legend controls expose accessible names/roles and are reachable in logical tab order And behavior conforms to WCAG 2.1 AA for 2.1.1 Keyboard, 2.4.3 Focus Order, and 2.4.7 Focus Visible
Performance Under Drag/Zoom/Paint with Overlay
Given a reference dataset of up to 25 team members and one guest over a 4-week view at 15-minute granularity When a user drags the timeline, zooms, or paints acceptable hours with the overlay active Then average frame rate is >= 55 fps And 95th percentile input-to-paint latency is <= 100 ms And no single main-thread task exceeds 50 ms during continuous interaction And peak memory usage increases by no more than 100 MB compared to baseline without overlay
Guest Constraints Applied Without Modifying Team Rules
Given existing team work hours and focus blocks are configured When a guest overlay is added and later removed Then team configuration values remain unchanged in storage and UI And no team rule mutations are logged or saved as a side effect And the overlay applies the guest’s constraints in parallel solely for visualization and intersection calculations
Mutual Sane Windows Visualization
Given team acceptable hours are painted and a guest overlay is active When computing intersections between team acceptable hours and the guest’s working hours excluding guest holidays Then mutual windows are visually distinguished from non-overlap cells using the treatment specified in the design tokens And a legend entry labeled "Mutual Windows" is present And disabling the guest overlay removes the mutual window treatment And the start/end timestamps of mutual windows match a back-end or deterministic client calculation within a tolerance of 1 minute
Holiday & Locale Detection for Guest
"As a meeting organizer, I want the guest’s public holidays to be reflected in the overlay so that I don’t propose meetings on days they are out."
Description

Determine and apply the guest’s public holidays based on their selected time zone and, when available, a user-confirmed country/region. Provide sensible defaults when only a time zone is known and allow manual override of country/holiday calendar. Fetch holiday calendars from a reliable source, cache them, and handle yearly rollovers. If no holiday data is available, clearly indicate that the overlay excludes holidays. This ensures the heatmap accurately reflects non-working days for the guest and avoids proposing impractical dates.

Acceptance Criteria
Default Holiday Detection with Time Zone Only
Given a guest is added to the overlay with only a time zone selected and no country/region provided When the overlay renders the guest’s availability heatmap Then a default country holiday calendar mapped to the time zone is applied And the applied country code/name is displayed as a label (e.g., "Holidays: AU — Default") And all holidays in the calendar are marked as non-working days on the heatmap And the UI presents a control to confirm or change the country/region
User-Confirmed Country/Region Overrides Time Zone Default
Given a guest has a time zone and a default holiday calendar applied When the user selects and saves a specific country/region for the guest Then the holiday calendar switches to the selected country/region And the heatmap updates to reflect the new holidays And the label updates to "Holidays: <Country[/Region]>" And the selection persists for the duration of the overlay session and in any generated share link
Manual Holiday Calendar Override and Reset
Given the holiday calendar selector is open for the guest When the user selects a different holiday calendar option (e.g., "US Federal (Observed)") Then the heatmap reflects the new calendar within 800 ms And a "Reset to Default" option restores the time zone–based default calendar And on reset the label reverts to "Holidays: <Default Country> — Default" And the chosen/reset calendar persists in any generated share link for that guest
Holiday Data Fetching and Caching
Given the holiday calendar for a country/region and year is not present in cache When it is first requested Then the system fetches it from the configured provider over HTTPS And the response is cached keyed by country/region + year And subsequent requests for the same key within 24 hours do not trigger a network call And on fetch failure the system records the error and does not retry more than 2 additional times within 5 minutes
Year Boundary and Rollover Handling
Given the visible scheduling window spans two calendar years for the guest’s local time When the overlay renders holidays within the window Then holidays from both the current and next year are displayed correctly And the next year’s holiday calendar is fetched and cached no later than the first view that includes dates from that year And observed-holiday rules across Dec 31/Jan 1 are applied correctly according to the selected calendar
No Holiday Data Available Notice
Given the provider returns no data or an error for the selected country/region and year When the overlay renders the guest’s heatmap Then a non-blocking notice states "Holiday data unavailable for <Country[/Region]>; holidays excluded" And no holiday shading is applied for the guest And the label changes to "Holidays: None (Unavailable)" And the same notice appears in any generated share link
Holiday Accuracy and Localization
Given the guest’s country is set to United States and the year is 2025 When viewing July 2025 in the guest’s local time zone Then July 4, 2025 is marked as a holiday and adjacent days are not, unless observed rules apply And if the holiday falls on a weekend, the observed weekday is marked instead per the selected calendar And holiday non-working intervals are applied 00:00–23:59 in the guest’s local date And region-specific holidays are only applied when a region is selected; otherwise only national holidays are shown
Ephemeral Share Link Generation
"As a coordinator, I want to share a temporary preview of proposed times with a guest so that they can confirm options before I send a formal invite."
Description

Create a secure, time-bound share link that encodes the current overlay state (guest context, proposed windows, timeline position) for external validation. Links should be tokenized, revocable, and configurable with TTL and view limits. The shared view must be read-only, require no Timeglue account, and present minimal necessary context (no team internals). Support deep-linking back to the editor for the owner and capture basic open metrics. This enables recipients to review candidate windows before any formal invite is sent, reducing back-and-forth.

Acceptance Criteria
Create Tokenized Share Link with TTL and View Limit
Given I have an active overlay with a guest and at least one proposed window And I select a TTL between 1 hour and 14 days and a view limit between 1 and 100 When I click "Generate Share Link" Then a URL is returned within 1 second that contains a cryptographically random token with ≥128 bits of entropy And the token is stored server-side with the selected TTL and view limit And the link resolves to the encoded snapshot referenced by that token
Snapshot Encodes Overlay State
Given I generate a share link from the editor When a recipient opens the link Then the shared view renders the guest time zone and observed holidays, the proposed windows, and the timeline scroll/zoom position exactly as in the editor at generation time And no unsaved editor changes after generation affect the shared view And generating a new link after editor changes produces a view that matches the updated editor state
Read-Only Public View Without Account and Minimal Context
Given a recipient opens the share link in a fresh browser session When the page loads Then no sign-in or account creation is required to view And all editing interactions are disabled (e.g., adding/removing guests, modifying windows, dragging timeline) And only minimal context is shown: guest time zone and holidays, proposed windows, and owner display name (first name only) And no team internals are displayed (no team roster, internal calendars, focus block labels, or internal notes)
TTL Expiry and View-Limit Enforcement
Given a share link is created with TTL = 24 hours and view limit = 3 When the link is opened 3 times within the TTL window Then the 4th and subsequent attempts return a "View limit reached" page with HTTP 403 and reveal no overlay details When the server-side TTL elapses Then any subsequent attempt returns a "Link expired" page with HTTP 410 and reveal no overlay details And further opens after expiry or limit are not counted in metrics
Owner Revocation and Post-Revocation Behavior
Given I am the owner of a share link When I revoke the link from the share management UI Then any subsequent access to that link returns a "Link revoked" page with HTTP 410 and reveals no overlay details And previously loaded sessions cannot refresh to regain access And the link status is updated to Revoked in the owner’s dashboard within 10 seconds
Owner Deep-Link Back to Editor
Given I am authenticated as the share link owner When I open my own share link Then an "Edit in Timeglue" control is visible and navigates to the original editor state for that share And if I am not authenticated as the owner, the control is not rendered in the DOM and no editor URL is exposed in the page source or network calls
Open Metrics Capture and Owner Visibility
Given a share link exists When the link is opened by a recipient Then the system records an open event with timestamp (UTC), anonymized visitor id, user agent, and country code And bot/unfurl traffic from known crawlers is excluded where detectable And the owner dashboard shows first-open time, total views, and unique visitors for the link And metrics are visible to the owner within 60 seconds of an open
Privacy & Data Minimization Safeguards
"As a privacy-conscious user, I want the guest preview to avoid storing or exposing personal details so that we can validate times without risking data leakage."
Description

Ensure the Guest Overlay workflow does not persist external PII beyond what is required for the session and ephemeral shares. Mask guest emails in UI and links where possible (e.g., first/last character reveal), encrypt tokens at rest, and avoid writing guest data to team settings or calendar providers. Provide explicit retention rules (e.g., auto-expire share links and associated session data after a short period) and surface a clear privacy notice. Log access events for security auditing without storing full identifiers. These safeguards maintain trust and compliance while enabling collaboration.

Acceptance Criteria
Masked Guest Identifier in UI and Links
Given a user inputs a guest email into Guest Overlay, When the overlay renders, Then any displayed guest identifier shows only the first and last character of the local part with remaining characters replaced by • and the domain displayed unmasked (e.g., j••••e@example.com). Given a shareable link is generated, When inspecting the URL, Then it contains only an opaque token and no email, name, or time zone as query or path parameters. Given masking is applied, When the user copies the guest identifier from the UI, Then the copied value remains masked and no control exists to reveal the full email within this feature.
Session-Only Handling of Guest Inputs
Given a guest is added by email or time zone, When the user does not generate an ephemeral share, Then no guest email or name is written to persistent storage and the data is cleared upon overlay close or after 30 minutes of inactivity, whichever occurs first. Given a guest is added, When querying application databases during and after the session, Then no records persist the guest email or name. Given the session ends, When a new session starts, Then previous guest entries do not auto-populate or appear as suggestions.
Ephemeral Share Link Lifecycle and Retention
Given a user generates an ephemeral share, When the link is created, Then a server-side record is created with a TTL of 24 hours and only minimal metadata (masked guest identifier, time zone, createdBy, createdAt) is stored. Given 24 hours elapse or the link is manually revoked, When the link is accessed, Then the endpoint returns HTTP 410 Gone with an "Expired" message and no PII in the response body or headers. Given a link expires or is revoked, When background cleanup executes, Then all associated session data (guest windows, masked identifier, token) are purged within 5 minutes.
No Propagation to Team Settings or Calendar Providers
Given the guest overlay is used, When inspecting team settings after use, Then no guest identifiers or preferences are stored in team configuration. Given availability is previewed, When monitoring outbound requests to calendar providers, Then no guest email, name, or identifier is transmitted; only internal availability computations occur and no calendar events are created or modified. Given calendar sync jobs are running, When the guest overlay is active, Then the system does not create, modify, or pre-stage any events in connected calendars related to the guest.
Token Security and Storage Controls
Given an ephemeral share is created, When storing its token server-side, Then the token is stored encrypted at rest using AES-256-GCM with keys managed by KMS and is never stored in plaintext. Given key management policies, When inspecting KMS configuration, Then encryption keys used for tokens are rotated at least every 90 days and access is restricted by role-based policies. Given a token is presented, When validating access, Then the system decrypts and compares in constant time and the raw token is only held in memory for the duration of the request.
Privacy Notice and User Disclosure
Given the user opens Guest Overlay for the first time in a workspace, When the overlay loads, Then a privacy notice is displayed describing masking, non-persistence, 24-hour share expiration, and pseudonymous logging, with a link to the full privacy policy. Given the notice is displayed, When the user attempts to generate an ephemeral share, Then they must acknowledge the notice once per workspace and the acknowledgment is stored without guest PII. Given subsequent sessions, When the overlay loads, Then a persistent Privacy link is visible to reopen the notice at any time.
Pseudonymous Security Audit Logging
Given a Guest Overlay action occurs (add guest, generate share, access share), When an audit log entry is written, Then it includes timestamp, actor workspace/user ID, action, resource token identifier, and a salted hash of the guest email if provided, without storing the plaintext email. Given log retention policy, When 90 days elapse, Then audit entries are automatically purged or archived per policy and contain no plaintext guest identifiers. Given logs are exported for security review, When reviewing the export, Then no plaintext guest emails are present and hashed identifiers are non-reversible using stored information.
One-click Convert to Formal Invite
"As an organizer, I want to convert an agreed preview into a formal invite with one click so that I can finalize the meeting without redoing the setup."
Description

Allow users to promote a validated overlay state into a formal Timeglue invite in a single action. Pre-fill attendees, mutually acceptable windows, and constraints while re-checking against team work hours, focus blocks, and the guest’s time zone and holidays. Provide conflict detection and suggest nearest compliant alternatives if a chosen slot is no longer valid. Hand off to the existing invite flow (including ICS generation and calendar integration) without duplicating configuration. This streamlines handoff from preview to commit, reducing errors and steps.

Acceptance Criteria
One-Click Convert Creates Prefilled Invite Draft
Given a validated guest overlay with at least one mutually acceptable time window selected And the overlay includes either a guest email or a time-zone-only placeholder When the user clicks "Convert to Invite" Then a single invite draft is created pre-filled with attendees (guest, selected team members), the selected mutually acceptable windows, and all overlay constraints (work hours, focus blocks, duration, buffers, meeting type/location) And meeting times are displayed in both organizer and guest time zones And the invite draft view loads within 2 seconds at p95 under normal load
Revalidation Against Hours, Focus Blocks, Time Zone, Holidays
Given a pre-filled invite draft created via conversion When conversion occurs Then the system re-validates proposed windows against current team work hours, focus blocks, buffers, minimum notice, and the guest’s time zone and holidays And any window failing validation is removed and flagged with a machine-readable reason code And the user sees an inline notice summarizing the count and reasons of removed windows
Conflict Detection and Nearest Alternative Suggestions
Given at least one selected window became invalid during re-validation When the draft loads Then a conflict banner appears explaining why the original window(s) are invalid And the system suggests at least 3 compliant alternative time options within the next 5 business days (or all available if fewer exist) And suggestions are sorted by proximity to the removed slot and highest heatmap score And selecting a suggestion updates the draft immediately without leaving the flow
Seamless Handoff to Existing Invite Flow (No Duplication)
Given a converted invite draft When the user proceeds to send Then the existing invite flow is used for ICS generation and calendar integration without re-entering configuration And no duplicate drafts or calendar events are created (idempotent on retries within 60 seconds) And all invite fields reflect overlay-derived values unless explicitly edited by the user
Permissions and Audit Log for Conversion
Given the user lacks permission to create invites on the selected calendar When they click Convert to Invite Then conversion is blocked with a clear permission error and no draft is created And an audit log entry is recorded with user ID, overlay ID, action=convert_invite, outcome=denied Given the user has permission When they convert successfully Then an audit log entry is recorded with user ID, overlay ID, action=convert_invite, outcome=success, and resulting invite ID
Handling Missing Guest Email on Conversion
Given the overlay was created by time zone only without a guest email When the user clicks Convert to Invite Then the system prompts for a guest email and validates format before proceeding And conversion cannot continue until a valid email is provided And upon entry, the draft is created with that guest as an attendee and all overlay selections preserved And no changes are made to the team setup or saved contacts by default

Risk Dials

Tune the canvas to today’s priorities with adjustable weights for factors like focus blocks, after‑hours tolerance, holidays, and prime‑hour protection. The heatmap recalculates instantly, revealing the best windows for launch weeks vs. steady state.

Requirements

Weighted Factor Controls
"As a remote team lead, I want to tune the importance of different scheduling factors with simple controls so that the suggested windows reflect today’s priorities."
Description

Provide an intuitive control panel with sliders/toggles to adjust weights for key scheduling factors (focus blocks, after‑hours tolerance, holidays, prime‑hour protection, travel buffers). Changes apply to the active canvas and persist per user profile. Include default values, inline help/tooltips describing each factor’s effect, and reset-to-default. Controls must be keyboard-accessible, support screen readers, and work on desktop and mobile. Settings sync across sessions and devices and are scoped to workspace when applicable.

Acceptance Criteria
Instant Heatmap Recalculation on Weight Adjustment
Given an open scheduling canvas with default weights and the control panel visible When the user adjusts any weight slider (e.g., increases "Focus blocks") Then the heatmap and recommended windows update to reflect the new weighting within 500 ms on desktop and within 1000 ms on mobile mid-tier devices And the numeric value adjacent to the control reflects the updated weight immediately And no stale state is displayed (spinner or skeleton shown if computation exceeds thresholds)
Complete Factor Set and Valid Ranges
Given a user opens the Weighted Factor Controls panel Then the following adjustable factors are present: Focus blocks, After-hours tolerance, Holidays, Prime-hour protection, Travel buffers And each factor exposes a weight control with a range 0–100 and step of 1 And values outside the range cannot be entered; inputs snap to the nearest valid step And each control displays the current numeric value and has a unique, visible label
Defaults and Reset-to-Default Functionality
Given a first-time user opens the control panel Then each factor displays its system default value as defined by the product configuration When the user selects "Reset to default" Then all factor values revert exactly to their system defaults in one action And the heatmap recalculates to reflect defaults within the same performance thresholds And the "Reset to default" control is disabled when current values already match defaults
Inline Help and Tooltips for Each Factor
Given the control panel is visible When the user hovers, focuses, or taps the help icon for any factor Then a tooltip appears within 200 ms explaining how the factor influences scheduling and window scoring in plain language And the tooltip remains visible while focused/hovered and dismisses on Escape or blur And the tooltip is associated to the control via aria-describedby so screen readers announce it
Accessibility: Keyboard and Screen Reader Support
Given the user navigates via keyboard only Then all controls are reachable in a logical tab order and have a visible focus indicator meeting 3:1 contrast And sliders are operable with Arrow keys (±1), Page Up/Down (±10), and Home/End (min/max) And toggles activate with Space/Enter And each control exposes role, name (e.g., "Focus blocks weight"), current value, min, and max to assistive technologies per ARIA Authoring Practices And no keyboard traps or motion-only interactions are required (WCAG 2.1 AA)
Per-User Persistence and Cross-Device Sync
Given a signed-in user adjusts any factor weight When the user refreshes the page or signs out and back in Then the most recent weights are restored and applied to the active canvas And when the user signs in on a second device, the same weights load for that user profile after authentication And changes save within 2 seconds of adjustment (visible save state or silent), surviving network blips without data loss
Workspace-Scoped Settings Behavior
Given a user belongs to multiple workspaces (A and B) When the user sets weights in workspace A and then switches to workspace B Then the control panel in B displays B's stored weights (not A's) When the user returns to workspace A Then A's previously saved weights are shown And changing weights in one workspace does not alter the other workspace's settings
Instant Heatmap Recalculation
"As a scheduler, I want the heatmap to update immediately when I adjust weights so that I can see the impact and choose the best window quickly."
Description

Recompute and redraw the availability heatmap in near real time when factor weights change, without page reloads. The scoring engine must combine participant work hours, focus blocks, holidays, and prime-hour protections using the current weights and update visible slots within 150 ms for up to 12 participants across 8 time zones. Use debounced input, off-main-thread computation (e.g., Web Workers), and incremental rendering to avoid UI jank. Maintain correctness with partial data and show a lightweight loading state when recomputation exceeds threshold. Log performance metrics for ongoing tuning.

Acceptance Criteria
P95 Heatmap Update Latency Under Typical Load
- Given up to 12 participants across 8 time zones and an active canvas, when any risk dial weight changes, recompute scores and redraw all visible heatmap cells within 150 ms P95 and 220 ms P99 measured from final debounced input to first paint completion. - No full page reload occurs; navigation count remains unchanged, and only affected heatmap tiles are updated. - Cumulative layout shift (CLS) during update is ≤ 0.02. - Main thread records 0 long tasks (>50 ms) for ≥95% of recomputation events.
Debounced Risk Dial Adjustments
- Rapid dial changes within a 100 ms window coalesce into a single recomputation; last value wins. - During continuous dragging, recomputation frequency does not exceed 10 Hz (≥100 ms between runs). - On dial release, a final recomputation runs with the last value and completes visible updates within 150 ms P95. - No intermediate UI flicker; intermediate frames reflect either the previous stable state or the latest coalesced state.
Off-Main-Thread Scoring With Smooth UI
- Scoring computation executes in a Web Worker; no synchronous scoring occurs on the main thread. - While recomputation is in progress, input latency for hover/scroll/drag remains ≤ 50 ms P95; UI frame rate stays ≥ 55 FPS P95. - Main-thread long tasks (>50 ms) per recomputation = 0 for ≥95% of events; any serialization/deserialization step per batch ≤ 5 ms.
Incremental Heatmap Rendering
- Only tiles within the visible viewport are re-rendered; offscreen tiles are deferred until scrolled into view. - Progressive paint updates at least 60% of visible tiles within 100 ms and the remainder within the next 100 ms (total ≤ 200 ms) without blocking pointer events. - If no effective weight change occurs, no re-render is triggered (idempotent updates verified via unchanged tile hash). - Memory usage returns to within 5% of baseline within 2 seconds after drag stop; no sustained growth across a 2‑minute continuous adjust test.
Partial Data Handling and Lightweight Loading State
- When one or more participant calendars are unavailable, scores are computed using available data and marked as partial; no errors or empty states block interaction. - Upon late arrival of missing data, recomputation merges the new data and updates visible tiles within 150 ms P95. - A lightweight loading indicator appears only when a recomputation exceeds 120 ms or when awaiting remote data; indicator is non-blocking and disappears within 50 ms after completion. - Visual consistency is maintained: no color scale jumps beyond the updated tiles; no spinner overlaps controls.
Weighted Scoring Correctness
- For a canonical fixture set, computed slot scores match a reference implementation within ±0.5% with current weights applied to work hours, focus blocks, holidays, and prime-hour protections. - Changing a single dial produces monotonic score adjustments in the expected direction for affected intervals; unaffected factors remain unchanged. - Latest-value wins: if multiple weight changes are in-flight, the highest sequence (most recent) is the one applied to the rendered heatmap. - Time zone normalization is correct across 8 time zones; daylight saving transitions are handled per IANA rules with no off-by-one-hour errors.
Performance Telemetry and Threshold Alerts
- Each recomputation logs participant count, time zone count, debounce wait, worker time, main-thread time, total time to first paint, and min/avg FPS; event size ≤ 2 KB; no PII included. - Dev environment sampling = 100%; production sampling = 10%, adjustable via config without deploy. - If rolling 10-minute P95 total time > 150 ms or P99 > 220 ms, a warning metric is emitted and visible on the dashboard within 1 minute. - Telemetry failures do not impact UI flow; retries are backoff-capped and remain under 1% CPU overhead.
Risk Presets and Profiles
"As a team lead, I want ready-made and saveable weight profiles so that I can switch the scheduling strategy for different scenarios without re-tuning each time."
Description

Offer curated presets (e.g., Launch Week, Steady State, All‑Hands, Customer Call) that preconfigure factor weights for common scenarios. Allow users to create, name, save, and update custom profiles, set a default profile, and switch with one click. Support workspace-level shared presets managed by admins and personal presets for individuals. Provide import/export of profiles as JSON for portability and versioning with change history.

Acceptance Criteria
Select Curated Preset Applies Risk Weights and Recalculates Heatmap
Given a canvas with Risk Dials and curated presets (Launch Week, Steady State, All-Hands, Customer Call) When the user selects any curated preset Then the preset’s factor weights are applied to the Risk Dials And the heatmap recalculates and renders updated windows within 500 ms And the active preset name is displayed in the profile switcher
Create and Save Personal Custom Profile
Given current factor weights and a valid profile name When the user selects “Save as New Profile” Then a personal profile is created with the provided name and current weights And it appears under Personal Profiles immediately And it persists across sessions for the same user account And names are unique within Personal Profiles; attempts to reuse a name require choosing a different name
Update Custom Profile with Versioned Change History
Given an existing personal profile and modified factor weights When the user saves changes to that profile Then the profile’s version number increments by 1 And a change history entry is recorded including timestamp, actor, previous weights, and new weights And the updated profile is applied to the current canvas
Set and Apply Default Profile for New Canvases
Given a user sets a profile as Default When the user opens a new scheduling canvas Then the default profile is automatically applied to the Risk Dials And a default indicator shows the active default profile And switching the profile on a canvas does not change the saved default And the default selection persists across sessions
One-Click Profile Switching with Accessibility
Given the Profiles switcher is visible and focused When the user selects a different profile with a single click or presses Enter on a focused option Then the selected profile applies immediately without additional confirmation And the switcher closes and returns focus to the trigger control And the control is keyboard navigable and announced by screen readers with the active profile name And application time is under 500 ms for canvases up to 50 participants
Admin-Managed Workspace Presets and Permissions
Given a user with Admin role creates or updates a Workspace Preset When the change is saved Then the preset is available under Workspace Presets to all workspace members within 5 seconds And non-admin users cannot create, edit, or delete Workspace Presets And each change records audit metadata (actor, timestamp, change summary) And Personal Profiles remain unchanged by admin actions
Import and Export Profiles as JSON with Validation and Merge
Given one or more selected profiles When the user exports profiles Then a JSON file downloads containing each profile’s name, scope (personal/workspace), factor weights, version, and change history with a schemaVersion field When the user imports a profiles JSON file Then the file is validated against the schema; invalid files show a descriptive error and make no changes And on name conflicts the user can choose to skip, rename, or overwrite; the default action is skip And workspace-scope imports require Admin role and place profiles in Workspace Presets; personal imports go to Personal Profiles
Sharable Risk Links
"As a coordinator, I want to share a link that encodes today’s risk settings so that collaborators evaluate times using the same criteria."
Description

Enable smart links that capture the current weight configuration (or selected preset) and apply it for recipients when opening the scheduling canvas. Support optional parameters: read-only vs. allow-copy, expiry, and workspace scoping. Ensure secure encoding of settings, respect recipient permissions, and display a banner indicating the active shared configuration with an option to adopt or revert.

Acceptance Criteria
Open Shared Risk Link: Apply and Banner
Given a valid shared risk link encoding a Risk Dials configuration or preset When a recipient opens the link Then the scheduling canvas initializes with the encoded weights/preset applied to the Risk Dials And the availability heatmap reflects the applied configuration within 1 second of canvas render And a banner indicates "Shared configuration active" with "Adopt" and "Revert" actions And selecting Revert restores the recipient's prior local configuration for the current workspace within the current session And selecting Adopt persists the shared configuration as the recipient's local default for this workspace until changed by the recipient
Share Captures Weights or Preset
Given a user has adjusted Risk Dials to custom weights or selected a named preset When the user generates a shareable risk link Then the link encapsulates either the full weight vector with a configuration version and checksum, or a preset identifier with its version And opening the link reproduces identical effective weights in the recipient's canvas And when a preset is shared, the banner displays the preset name; when custom weights are shared, it displays "Custom"
Sharing Permissions: Read-only vs Allow-copy
Given a link created with mode=read-only When a recipient opens the link Then Risk Dials controls are non-editable And no "Copy to My Presets" or "Save to Workspace" action is shown And Adopt/Revert actions in the banner remain available And any attempt to invoke copy/save endpoints is blocked with 403 Forbidden Given a link created with mode=allow-copy When a recipient with permission to create presets opens the link Then the banner shows a "Copy to My Presets" action And selecting it creates a new personal preset with identical weights and source metadata And recipients without create-preset permission do not see the copy action
Link Expiry Enforcement
Given a shareable risk link with expiry timestamp T When the link is opened after T Then the shared configuration is not applied And an inline message states the link has expired and shows created/expiry timestamps And the resolver endpoint responds with HTTP 410 Gone When the link is opened before T Then the configuration applies normally And the banner indicates the remaining time until expiry
Workspace Scoping and Permissions
Given a link scoped to Workspace A When opened by a member of Workspace A Then the configuration applies And Adopt/Copy options are available or restricted according to the member's role permissions in Workspace A When opened by a non-member of Workspace A Then the configuration applies only within a sandboxed session context And workspace-managed preset names/IDs are not exposed; the banner shows a generic source label And no option to save to Workspace A is presented And Adopt persists only to the recipient's local profile (not to Workspace A)
Secure Encoding and Tamper Detection
Given a generated shared risk link Then its token is signed and verifiable, contains only opaque identifiers and configuration data (no user PII or raw workspace IDs), and is no longer than 2048 URL characters When any part of the token is altered Then validation fails, the shared configuration is not applied, the banner shows an "Invalid link" message, and the resolver responds with HTTP 400 and error code LINK_TAMPERED And links with an embedded expiry are rejected after expiry during validation
Version Compatibility and Fallback
Given a shared link with configuration version V When opened by a client that does not support one or more factors in V Then unknown factors are ignored with server-side normalization, known factors apply correctly, and the banner warns of partial application And opening the same link on a client that supports V applies 100% of settings And links with a version higher than the server-supported maximum are rejected with HTTP 426 Upgrade Required and the recipient's local configuration remains unchanged
Slot Score Breakdown
"As a decision-maker, I want to see why a slot is rated high or low so that I can justify the pick and address conflicts if needed."
Description

Provide an on-hover or tap panel that explains a time slot’s score by factor (e.g., +8 prime hours, −5 after-hours penalty, −3 focus block conflict, +2 holiday alignment). Highlight the top contributors and blockers, and link to the relevant calendar items or policies when possible. Allow copying the breakdown to clipboard for async decision-making. Ensure the explanation mirrors the exact scoring formula to build trust.

Acceptance Criteria
Desktop Hover Score Panel
Given a desktop user hovers over a time slot for ≥150ms When the pointer remains within the slot Then a score breakdown panel appears within 200ms anchored to that slot And the panel displays: total score and a per‑factor list with factor name and signed numeric contribution (e.g., +8, −5) and percentage of total And factors are sorted by absolute contribution descending And the top 3 positive contributors and top 3 negative contributors are visually highlighted and labeled; ties include all tied items And the panel remains visible while hovered and for ≥300ms after hover leaves the slot; moving focus outside both slot and panel dismisses it And pressing Esc closes the panel
Mobile Tap Score Panel
Given a touch device When a user taps a time slot Then the score breakdown panel opens within 250ms anchored to that slot And a second tap on the slot or a tap outside the panel closes it And the panel supports vertical scroll if content exceeds viewport and maintains ≥44px touch targets And a downward swipe gesture on the panel closes it And the panel does not open during inertial scrolling; only deliberate taps trigger it
Copy Breakdown to Clipboard
Given the score breakdown panel is open When the user activates the Copy action Then the full breakdown is copied to the clipboard as plain text And the copied text includes: slot date/time in each participant’s time zone, total score, each factor with signed contribution and any available source URL, and the current Risk Dials weights snapshot And a confirmation toast “Breakdown copied” appears within 200ms And all numbers, signs, ordering, and highlights in the copied text match the on‑screen values
Formula Parity and Live Recalculation
Given a slot is selected and current Risk Dials weights are set When any weight is adjusted Then the slot’s total score and each per‑factor contribution in the panel update within 300ms without page reload And the algebraic sum of displayed factor contributions equals the displayed total score exactly after applying the defined rounding rule And the rounding rule is consistent between engine and UI (integer shown with 0 decimals; non‑integer shown with 1 decimal) And displayed values match the scoring engine outputs for representative test slots within ±0.0 after rounding
Context Links and Permissions
Given a factor has an associated calendar event or policy When displayed in the panel Then the factor entry includes a hyperlink to the source if the user has access And if the user lacks access, the entry shows a non‑clickable label “Restricted” with a tooltip and no URL exposure And clicking an event link opens it in the default calendar in a new tab with rel=noopener and target=_blank; policy links open in a new tab similarly And private events do not expose PII; if title is hidden, display “Private event”
Accessibility and Keyboard Support
Given keyboard navigation When a time slot has focus Then pressing Enter or Space opens the score breakdown panel and Esc closes it And focus is trapped within the panel while open and returns to the originating slot on close And the panel has role=dialog with an accessible name “Slot score breakdown”; factor rows expose names and values to assistive tech And all text and highlight styles meet WCAG 2.1 AA contrast And the Copy control is reachable via Tab and has aria‑label “Copy breakdown”
Edge Cases and Empty States
Given a slot with total score 0 and no contributing factors When the panel opens Then it displays total score 0 and the message “No contributing factors” And when >10 factors exist, initially show the top 10 by absolute contribution with a “Show all” control to reveal the rest And if the scoring service errors or times out, show a non‑blocking error message with a Retry action; the canvas remains stable And if offline, show “Offline — showing last known breakdown” when cached data exists; otherwise show an offline notice And if highlight thresholds tie, include all tied items as highlighted And times are shown with local time zone abbreviation and UTC offset for each participant
Admin Policy Bounds
"As an admin, I want to set guardrails on risk settings so that our organization’s scheduling stays within agreed norms and compliance requirements."
Description

Allow workspace admins to define min/max bounds and defaults for weights (e.g., cap after-hours tolerance, enforce prime-hour protection) at workspace or team level. Display guardrails in the UI and prevent users from exceeding policy limits, with clear messaging. Include an audit log of changes to policies and overrides, and apply policies to presets and shareable links.

Acceptance Criteria
Workspace Admin Sets Weight Bounds and Defaults
Given I am a workspace admin on the Policy Settings page When I set After-hours tolerance min=0, max=30, default=10 and Prime-hour protection min=70, max=100, default=90 and click Save Then the policy is persisted and a subsequent GET to retrieve workspace policy returns those exact values And defaults are applied to new users’ Risk Dials on first load Given I attempt to set any default outside its min/max When I click Save Then the save is rejected with field-level error "Default must be within bounds" and no changes are persisted Given I attempt to set any min or max outside 0–100 or with min >= max When I click Save Then the save is rejected with field-specific errors and HTTP 422-equivalent response
Team Policy Inherits and Narrows Workspace Bounds
Given workspace After-hours tolerance bounds are min=0, max=30 When a team admin sets team After-hours tolerance to min=0, max=20 and saves Then the team policy saves successfully and the effective team bounds are min=0, max=20 Given the same workspace bounds (min=0, max=30) When a team admin attempts to set team After-hours tolerance max=40 Then the save is rejected with error "Team bounds must be within workspace bounds" and no changes are persisted Given a user belongs to Team Alpha (After-hours max=20) and Team Beta (After-hours max=25) When the user opens Risk Dials Then the effective bound applied is max=20 (most restrictive) and this is shown in the policy tooltip
Risk Dials UI Enforces Bounds with Guardrails and Messaging
Given effective Prime-hour protection bounds are min=70, max=95 When the user drags the dial below 70 Then the value snaps to 70 and a guardrail indicator with tooltip "Capped by policy" is visible Given the same bounds When the user types 99 into the numeric input and blurs the field Then the value auto-corrects to 95 and helper text "Max 95 by policy" is shown Given a page is loaded with URL parameter prime_hour_protection=50 When the Risk Dials render Then the initial value is 70 and a one-time inline banner "Policy caps applied" appears Given disabled dial segments represent out-of-bounds ranges When the user hovers those segments Then a tooltip shows the policy source (Workspace or Team) and the bound values
Policies Apply to Presets and Shareable Links
Given a team After-hours tolerance max=20 When a user saves a preset with After-hours tolerance=35 Then the saved preset value is 20 and the preset metadata includes policy_capped=true Given the same bound When a user creates a shareable link with After-hours tolerance=35 Then the link resolves to a value of 20 on open and the landing view shows a banner "Policy caps applied" Given an existing shareable link created before the policy change contains After-hours tolerance=35 When a recipient opens the link after the policy is active Then the computed schedule uses 20 and no value >20 is present in the resolved payload Given the bound changes from max=20 to max=10 When a previously saved preset with value=20 is applied Then the applied value is 10 and a banner "Updated to new policy" is shown
Audit Log Records Policy Changes and Overrides
Given a workspace admin changes Prime-hour protection max from 100 to 95 and provides reason "Launch week" When they save the policy Then an audit log entry is created with fields: actor_id, actor_role, scope=workspace, setting=prime_hour_protection, old.max=100, new.max=95, reason="Launch week", timestamp (UTC ISO 8601), request_id, ip_address Given a team admin attempts to set bounds outside workspace bounds and is rejected When they attempt to save Then no policy-change audit entry is created and an audit event type=policy_change_rejected is recorded with validation_errors and actor_id Given an approved temporary override is created for user U to allow After-hours tolerance max=40 until date D When the override is saved Then an audit entry is recorded with override_id, subject_user_id, effective_from, effective_until=D, old_bounds, new_temp_bounds, actor_id, timestamp
Retroactive Normalization of Existing Out-of-Bounds Weights
Given a saved preset P with After-hours tolerance=60 exists And the workspace sets After-hours tolerance max=20 When preset P is opened for editing or applied Then the value is clamped to 20 and a one-time banner "Updated to comply with workspace policy" is shown on that preset view And an audit entry is written with actor_id=system, change_type=auto_normalization, old.value=60, new.value=20, policy_version=current Given a shareable link L with After-hours tolerance=60 exists When L is opened after the policy change Then the resolved values clamp to 20 and a banner "Policy caps applied" is shown on the landing page
Risk Dials API & Webhooks
"As a developer, I want API access to adjust weights and retrieve scored windows so that I can embed Risk Dials logic in our internal workflows."
Description

Expose REST endpoints to list presets, read/update user or workspace weight settings, and compute scored availability windows given participants and a weight profile. Support signed deep links for external tools. Add webhooks for events such as preset applied, weights changed, and link opened. Include OAuth scopes, rate limits, and idempotency to integrate safely with calendars, CRMs, and internal tools.

Acceptance Criteria
List Risk Dial Presets via REST
Given a valid OAuth token with scope risk_dials.read When GET /v1/risk-dials/presets is called Then response status is 200 and body is an array of presets with fields {id, name, weights, scope, updated_at} And pagination supports limit (<=100) and cursor; next_cursor is returned when more results exist And responses include ETag; If-None-Match with current ETag returns 304 And unauthorized tokens return 401; insufficient scope returns 403 with code "insufficient_scope" And p95 latency <= 250ms for 100 presets
Update Workspace Risk Dial Weights with Validation and Concurrency Control
Given a token with scope risk_dials.write and workspace admin rights When PUT /v1/workspaces/{workspace_id}/risk-dials/weights with body {focus_blocks, after_hours_tolerance, holidays, prime_hour_protection} integers 0..100 Then response is 200 with persisted weights and version And sending values outside 0..100 or missing required fields returns 422 with field-level errors And If-Match with a stale version returns 412 Precondition Failed And Idempotency-Key reused within 24h with identical body returns the same result without creating duplicate updates And an audit event weights.changed is recorded with actor, workspace_id, before/after values
Compute Scored Availability Windows for Multi-Timezone Participants
Given a token with scope availability.compute and a list of participants (id or email, timezone, work hours, focus blocks, holidays) plus a weight preset id or inline weights When POST /v1/availability/score is called Then response 200 contains an array of windows with fields {start, end, score, reasons[]} sorted by score desc then start asc, timestamps in ISO-8601 UTC And no returned window violates any participant’s focus block or holiday beyond the configured after-hours tolerance And identical inputs produce identical outputs (deterministic) And invalid participant identifiers or timezones return 400 with validation details And p95 compute time <= 1.5s for up to 50 participants and a 14-day horizon And exceeding 60 requests/min per token returns 429 with Retry-After
Signed Deep Links for Preloaded Risk Profiles in External Tools
Given a deep link token containing {resource_ref, exp <= 24h, nonce} signed with a workspace secret When GET /r/{token} is opened Then the user is redirected (302) to the app with the referenced preset/weights preloaded and scoped to that workspace And invalid signature or expired exp returns 403 with code "invalid_link" And replaying the same nonce within TTL returns 409 and does not reveal resource existence And token payload contains no PII; only opaque identifiers And an audit event link.opened is recorded with link_id, workspace_id, and hashed IP
Event Webhooks for Preset Applied, Weights Changed, and Link Opened
Given a subscription exists for events ["preset.applied","weights.changed","link.opened"] When any such event occurs Then a POST is delivered within 10s containing {id, type, created_at, workspace_id, data, schema_version} and header X-Timeglue-Signature (HMAC) with Content-Type application/json And non-2xx responses are retried with exponential backoff up to 12 attempts over 24h with jitter And deliveries include an Idempotency-Key matching event id so consumers can deduplicate And secret rotations allow validation with old and new secrets for 1h overlap And a test-delivery endpoint exists that sends a sample event and returns 200 upon successful dispatch
OAuth Scopes, Rate Limits, and Idempotency for Safe Integration
Given OAuth scopes {risk_dials.read, risk_dials.write, availability.compute, webhooks.manage, links.create} When a request is made without the required scope Then 403 is returned with code "insufficient_scope" and WWW-Authenticate lists required scopes And all unsafe methods accept Idempotency-Key; identical key+body within 24h executes at most once and returns the original result with the same request_id And standard rate limits are enforced per token (600 req/min, burst 60); responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset; 429 includes Retry-After And errors follow application/problem+json with {type, title, status, code, detail, request_id} And responses support gzip when requested; P99 payload size <= 1 MB

Why Red

Click any high‑risk area to see exactly what’s blocking it—who’s after hours, which holiday applies, or which focus block conflicts—plus one‑click alternates that resolve the issue with minimal shift.

Requirements

Click-to-Reveal Conflict Details
"As a remote team lead, I want to click a red time window and immediately see the exact reasons it’s blocked so that I can make an informed scheduling decision without guessing or asking around."
Description

When the user clicks any red (high‑risk) zone on the scheduling timeline, open a contextual detail panel that enumerates each blocking factor with precise attribution: participant(s) outside work hours (with local time and work window), applicable holiday (country/region and holiday name), and conflicting focus block (source calendar and block title). The panel anchors to the selected time range, displays the user’s chosen slot, and highlights which attendees are impacted and why. Integrates with Timeglue’s timeline, participant profiles, and calendar sync to fetch real-time constraints. Provides clear, scannable tags for each cause, links to underlying source (e.g., calendar event), and non-color indicators to ensure clarity for all users. Expected outcome: users instantly understand why a time is unsafe without manual time‑zone math or back‑and‑forth messaging.

Acceptance Criteria
Click Red Zone Reveals Conflict Panel
Given a scheduling timeline with one or more red (high-risk) zones visible And participants have constraints causing the red state at the selected range When the user clicks a red zone or drags to select a sub-range within a red zone and releases Then a contextual detail panel opens within 300 ms of interaction end And the panel is positioned adjacent to the selected time range without obscuring the selection And the panel header shows the selected start and end in the viewer's local time and time zone abbreviation And the panel enumerates all blocking factors present at that exact range
Participant After-Hours Attribution
Given the selected range places at least one participant outside configured work hours When the panel renders Then each impacted participant entry includes: participant display name, local time at start and end, configured work window for that day (start–end with time zone), and a 'Outside work hours' tag And participants not impacted by after-hours are not listed under the after-hours section And times display in 12h or 24h format according to user preference
Holiday Attribution Details
Given the selected range overlaps an applicable holiday for any participant location When the panel renders Then it lists each holiday with: affected participant(s), country/region code, holiday name, and holiday date span And each holiday row includes a non-color icon and a 'Holiday' text tag And a 'View holiday' link opens the holiday source in a new tab And half-day/observed holidays are labeled accordingly
Focus Block Conflict With Source Links
Given the selected range overlaps a synced focus block event for any participant When the panel renders Then it shows for each conflict: participant name, provider name and calendar name, event title (or 'Private event' if hidden), and event start–end in participant's local time And a 'Open in provider' link deep-links to the event in the correct calendar provider in a new tab And link presence respects the user's integration permissions
Anchoring and Impact Highlighting
Given a time range is selected inside a red zone When the panel opens Then the selected slot is visually highlighted both on the timeline and in the panel header And impacted attendees are visually indicated on the participant list/avatar row without relying on color alone And hovering or focusing a factor row highlights the corresponding participant and time segment on the timeline And collapsing/expanding factor sections does not change the selected time range
Accessibility and Non-Color Indicators
- Panel can be opened via keyboard (Enter/Space) on a focused red zone and closed via Esc - Focus is trapped within the panel while open; focus returns to the triggering element on close - All factor indicators include text labels and icons; color is not the only means of communication - Screen readers announce: panel title, selected time, count of blocking factors, and each factor with participant names and times - Visible focus states are present for interactive elements; text/icon contrast ratio is >= 4.5:1 - Meets WCAG 2.1 AA checks (automated + manual)
Real-Time Sync, Freshness, and State Changes
Given calendars and profiles may change between interactions When the panel opens Then the system fetches the latest constraints for the selected range before rendering factor details And a 'Last updated' timestamp is shown with minute-level precision And if the slot is no longer high-risk, the panel displays 'No longer high-risk' and the timeline updates within 1 s And on fetch failure, an inline error with 'Retry' appears; last known data is shown and labeled 'Stale'
Root‑Cause Ranking & Conflict Aggregation
"As a meeting organizer, I want the main blocker highlighted and secondary issues grouped so that I can quickly assess severity and decide whether to adjust or escalate."
Description

Consolidate multiple overlapping constraints into a single explanation with ranked root causes. Apply a deterministic ranking model (e.g., policy overrides required > hard holiday > outside work hours > focus block) and show the top cause prominently with expandable secondary causes. Group identical conflicts (e.g., 4 attendees after hours) and present counts with drill‑down to individuals. Ensure the ranking model is configurable at the org level and supports localization of labels. Expected outcome: users see the most material blocker first and can drill down only as needed, reducing cognitive load.

Acceptance Criteria
Deterministic Root Cause Ranking Display
Given an org with the ranking model "Policy Override Required > Hard Holiday > Outside Work Hours > Focus Block" And a meeting time where at least two of those constraints are present across attendees When a user clicks a red high‑risk area in the timeline Then the Why Red panel renders a single consolidated explanation card And the first listed cause matches the highest‑ranked constraint present per the org model And all other present causes are listed below as secondary causes in descending rank order And secondary causes are collapsed by default behind an affordance indicating the number of additional causes
Stable Ordering and Determinism Under Recompute
Given a fixed set of attendees and constraints at a chosen meeting time And no changes are made to inputs or configuration When the user opens the Why Red panel 10 times and/or reloads the page Then the order of causes and grouped items is identical across openings And the same cause appears as the top cause on every opening And no jitter, shuffling, or inconsistent tie‑breaking occurs
Aggregation of Identical Conflicts With Accurate Counts
Given 4 attendees are outside their work hours and 2 attendees are on the same hard holiday "Labor Day" When the user opens the Why Red panel Then the panel shows a single "Outside Work Hours" cause line with a count of 4 And the panel shows a single "Hard Holiday — Labor Day" cause line with a count of 2 And no duplicate lines appear for the same cause type and same subtype And attendees on different holidays are grouped under distinct holiday line items with correct counts
Drill‑Down to Individuals From Aggregated Causes
Given a grouped cause line shows a count greater than 1 When the user expands the grouped cause Then a list of individual attendees is displayed with name, local date/time at the proposed meeting, and the specific blocking reason And collapsing the group hides the individual list without altering counts And the drill‑down interaction does not change the rank order of causes And selecting an individual attendee opens their detailed conflict view without leaving the panel
Organization‑Level Ranking Model Configuration Persistence
Given an org admin opens Org Settings > Conflict Ranking When the admin reorders the cause types to "Hard Holiday > Policy Override Required > Outside Work Hours > Focus Block" and saves Then the new order is persisted for the org And subsequent Why Red panels for any user in the org use the new order for ranking And reopening the settings reflects the saved order And non‑admin users cannot edit the ranking model And restoring defaults resets the order to the product default model
Localized Labels and Plurals in Explanation Panel
Given the org locale is set to "es‑ES" And translations exist for cause labels and UI strings When a user opens the Why Red panel with grouped causes including counts of 1 and 3 Then all labels (cause types, subtypes, headings, and action text) appear in Spanish And pluralization is correct for counts (e.g., "1 asistente", "3 asistentes") And any missing translation gracefully falls back to English without exposing placeholder keys And locale‑aware sorting is applied to any alphabetically ordered lists within the panel
One‑Click Minimal‑Shift Alternates
"As a scheduler, I want nearby conflict‑free alternatives I can apply with one click so that I can resolve issues without rebuilding the meeting from scratch."
Description

Offer one‑click suggestions that minimally shift the selected time to resolve conflicts (e.g., “+30m resolves after‑hours for 3 attendees”). Compute nearby viable windows using participants’ constraints, showing up to 3 closest options with a per‑option impact summary (who becomes compliant, who remains conflicted). Allow direct actions: replace selection, copy as smart link, or open in invite composer. Integrates with Timeglue’s suggestion engine and smart link sharing, honoring all policies and preferences. Expected outcome: users can fix conflicts with a single click, minimizing schedule churn.

Acceptance Criteria
Compute Minimal‑Shift Alternates Near Selected Time
Given a selected meeting start time T and duration D with at least one conflict And all participant constraints (work hours, holidays, focus blocks) and policies are loaded When the user opens the Why Red panel for the conflicted selection Then the system generates candidate windows by adjusting only the start time (keeping D constant) And the candidates are sourced from the suggestion engine honoring all active policies and preferences And candidates are sorted by absolute time shift |candidate_start − T| ascending And ties are broken by earlier start time first, then by fewer remaining conflicts And only candidates that resolve the triggering conflict are eligible
Display Up to Three Closest Viable Options
Given the system has computed candidate alternates for the conflicted selection When presenting alternates to the user Then show at most three options in order of minimal shift from the selected time And if fewer than three viable options exist, show only the available count And if no viable options exist, show an empty state message indicating no minimal‑shift alternates and a link to open the full Suggestions view And each option displays its shift relative to the original selection (e.g., "+30m", "-1h") And no duplicate or overlapping options are displayed
Accurate Per‑Option Impact Summary
Given a list of displayed alternate options When rendering each option’s impact Then each option shows the count of attendees becoming compliant and the count remaining conflicted And the impact summary lists the names (or identifiable labels) of attendees remaining conflicted and the conflict type (after‑hours, holiday, focus block) And the counts and lists exactly match the constraint evaluation for that option And no attendee appears in both compliant and conflicted lists for the same option
One‑Click Replace Selection
Given alternate options are visible for a conflicted selection When the user clicks Replace on an option Then the timeline selection updates to the option’s start time while keeping the same duration and attendees And conflicts are recalculated and the UI updates to reflect the new compliance state And the change is applied without altering any other meeting metadata (title, attendees, duration) And the action completes within 500 ms on a representative dataset
Copy Alternate as Smart Link
Given alternate options are visible for a conflicted selection When the user clicks Copy as Smart Link on an option Then a shareable URL representing that option (time, duration, attendees, context) is copied to the clipboard And a confirmation toast is shown within 1 second And opening the URL preselects the alternate in Timeglue with policies and preferences applied And if link sharing is disabled by policy, the user is shown a non‑destructive error and no URL is copied
Open Alternate in Invite Composer
Given alternate options are visible for a conflicted selection When the user clicks Open in Invite Composer on an option Then the invite composer opens with the option’s start time, duration, attendees, and timezone prefilled And the composer reflects current policies (e.g., no after‑hours) and will block send if violations are introduced afterward And the originating planner view remains intact and navigable And any composer launch failure surfaces an error without losing the user’s current context
Honor Policies, Work Hours, Holidays, and Focus Blocks
Given active global policies and per‑participant constraints are configured When generating and displaying alternates Then no displayed alternate violates enforced work hours, holidays, no‑meeting days, or blocking focus blocks for any participant And timezone‑localized evaluations are applied per participant correctly And if constraints change while the panel is open, alternates refresh to reflect the new rules before selection
Policy & Preference Enforcement with Guided Overrides
"As an operations manager, I want policy‑aware explanations and suggestions with controlled overrides so that our team stays compliant while retaining flexibility when justified."
Description

Honor org policies (e.g., no after‑hours meetings, holiday observance) and personal settings (work hours, focus blocks) in both explanations and alternates. If a suggested time requires an exception, clearly label the policy to be overridden and provide a guided override flow with required justification and optional approval routing (role‑based). Pull preferences from Timeglue profiles and synced calendars; reconcile conflicts by org policy precedence. Expected outcome: compliance by default, with auditable, intentional exceptions when necessary.

Acceptance Criteria
Default Suggestions Honor Policies and Preferences
Given org policies (e.g., no after-hours, holiday observance) and participant personal settings (work hours, focus blocks) are configured When Timeglue generates suggested times or alternates Then 100% of suggestions are compliant with all active policies and personal settings And the suggestions list is empty if no compliant times exist And each suggestion displays a "Compliant" indicator
Explicit Policy Labeling for Exceptions
Given the user selects a time that is blocked When the time requires an exception to schedule Then the UI lists each violating policy/preference with: name, source (Org|Personal), owner (if personal), and the specific rule segment (e.g., hours/holiday/block) And each listed violation includes a control to view policy details
Guided Override with Mandatory Justification
Given the user has permission to request overrides When the user initiates an override Then the system requires justification text of at least 20 characters And the user must explicitly select which policies to override (checkbox per policy) And the override cannot be submitted with missing required fields And upon submission, an audit record is created with meeting ID, policies overridden, before/after time, justification, requester ID, timestamp, and current approval status
Role-Based Approval for Policy Overrides
Given org approval rules are configured by role/team and policy type When an override requires approval Then the request is routed to the designated approver(s) within 5 seconds and a notification is sent And the meeting invite is not sent until approval is granted And if declined, scheduling is canceled and the requester is notified within 60 seconds And if no decision within 24 hours, the request escalates to the backup approver and both parties are notified
Policy Precedence Resolution
Given a conflict between an org policy and a personal preference When computing compliance and alternates Then org policy precedence is applied deterministically And the UI indicates the precedence decision (e.g., "Org policy overrides personal setting") And automated tests demonstrate correct precedence in at least 5 predefined conflict scenarios
Preference Sync and Freshness
Given Timeglue profile and connected calendars are configured When a user updates work hours, focus blocks, or holiday status Then the change is reflected in compliance checks and alternates within 5 minutes And if sync fails, last-known preferences are used for up to 24 hours with a visible "Sync Issue" banner and retry attempts every 10 minutes And each preference shows a last-synced timestamp accurate to within 1 minute
Alternates Resolve Violations with Minimal Shift
Given a blocked time is selected When the user requests alternates Then the system proposes up to 3 compliant alternates within ±60 minutes, sorted by minimal total participant shift in minutes And if fewer than 3 exist within ±60 minutes, expand search up to ±180 minutes and indicate the expansion to the user And each alternate resolves all identified violations with zero overrides required
A11y & Keyboard‑First Interaction
"As a keyboard and screen‑reader user, I want to access conflict reasons and alternates without relying on color or a mouse so that I can schedule effectively."
Description

Ensure the conflict detail panel and alternates are fully accessible: keyboard operable (tab order, shortcuts to open/close and navigate causes), screen‑reader friendly (ARIA roles, labels for cause tags and buttons), and perceivable without color (icons/text patterns in addition to red shading). Maintain WCAG 2.2 AA contrast ratios and provide focus visible states on the timeline. Expected outcome: all users, including those using assistive technologies, can discover and act on conflict reasons efficiently.

Acceptance Criteria
Keyboard Opening/Closing and Focus Management for Conflict Panel
Given focus is on a high-risk timeline segment or "Why Red" trigger, When the user presses Enter, Space, or Shift+W, Then the conflict detail panel opens as a modal, background content becomes inert, and focus moves to the panel title. Given the conflict panel is open, When the user presses Tab or Shift+Tab, Then focus moves through interactive elements in visual order (cause chips -> alternate options -> Close) without skipping any interactive element. Given the conflict panel is open, When the user presses Esc or activates Close, Then the panel closes and focus returns to the element that opened it. Given multiple causes are present, When the user presses ArrowUp/ArrowDown or J/K, Then focus moves to the previous/next cause and the details region updates; no page scroll occurs. Given the conflict panel is open, When the user Tabs past the last element or Shift+Tabs before the first, Then focus wraps within the modal (no escape to background via Tab).
Screen Reader Roles, Labels, and Announcements in Conflict Panel
Given the conflict panel opens, Then it exposes role="dialog" with aria-modal="true" and an accessible name that matches the visible title (e.g., "Why Red — 2 conflicts"). Given cause controls are rendered, Then each exposes a programmatic role and state (e.g., button/tab with selected=true when active) and an accessible name including cause type (after-hours/holiday/focus block), affected person/team, and relevant local time or holiday name. Given a cause selection changes, Then a polite live region announces the new selection summary and the count of available alternates. Given alternates are listed, Then they are exposed as a list with listitem semantics; each "Use alternate" control is a button whose accessible name includes the proposed date/time and shift delta. Given all interactive controls in the panel, Then none are unlabeled or have empty names; automated accessibility checks report zero critical violations for name, role, and value.
Perceivable Without Color: Conflict Indicators and Alternates
Given color information is unavailable (e.g., grayscale mode), When viewing the timeline and conflict panel, Then high-risk areas are identifiable via patterned overlays and an icon/text label (e.g., "Blocked") in addition to color. Given cause chips and badges, Then each includes an icon and textual descriptor of the cause; no state is conveyed by color alone. Given alternates that resolve conflicts, Then they include a check icon and "Resolves conflict(s)" text; success is not indicated by color alone. Given a color-blind simulation (protanopia/deuteranopia/tritanopia) or CSS grayscale(100%), Then all conflict states and resolutions remain distinguishable by icon and/or text without reliance on hue.
WCAG 2.2 AA Contrast for Panel, Timeline, and Focus Indicators
Given the conflict panel and timeline UI, Then body text and control labels have contrast ratios >= 4.5:1; large text (>= 18pt or >= 14pt bold) >= 3:1. Given non-text UI components (icons, borders, inputs, buttons) and states (hover/active/disabled), Then visual indicators have contrast >= 3:1 against adjacent colors. Given keyboard focus indicators on all focusable elements, Then the focus outline/indicator has a contrast ratio >= 3:1 against the adjacent color(s). Given both light and dark themes, Then the above contrast thresholds are met and verified by automated contrast tooling with zero AA failures.
Focus Visible and Not Obscured Across Timeline and Panel
Given any focusable element on the timeline or within the conflict panel, When it receives keyboard focus, Then a clearly visible focus indicator of at least 2 CSS pixels thickness is shown with contrast >= 3:1. Given a focused element within a scrollable container, When it receives focus, Then it is fully visible and not obscured by sticky headers/edges; the container scrolls it into view with at least 8px offset. Given an alternate is applied or the panel is closed, Then focus is not lost; it moves to the updated timeline slot or returns to the original trigger, respectively.
Keyboard Selection and Application of One‑Click Alternates
Given the conflict panel is open and an alternate option is focused, When the user presses Enter or Space, Then the alternate is applied, the timeline updates within 500 ms, and the conflict panel closes. Given the alternate is applied, Then a polite live region announces the result including the new meeting time and the number of conflicts resolved (e.g., "Alternate applied. New time 10:30 AM UTC. Conflicts resolved: 1"). Given the panel closes after applying an alternate, Then focus returns to the updated timeline slot or the original trigger element, ready for further keyboard interaction.
Fast Explanations with Caching (<200ms)
"As a user under time pressure, I want the reasons to appear instantly so that I can adjust the meeting without breaking my flow."
Description

Deliver conflict details within 200 ms for typical meetings (≤20 participants, ≤5 rule types) by precomputing common constraints, caching participant profiles, and using holiday/lookups in memory. Debounce repeated clicks and progressively render details (primary cause first, then expand). Fall back gracefully when data is stale or unavailable, indicating which elements may be outdated. Expected outcome: a snappy, uninterrupted experience that encourages investigation instead of avoidance due to latency.

Acceptance Criteria
Primary Cause Loads Under 200ms (Typical Meeting)
Given a meeting with ≤20 participants and ≤5 rule types and warm in-memory caches for participant profiles and holidays When the user clicks a red high‑risk area Then the primary blocking cause and impacted participant(s) render within 200 ms (p95 over 100 interactions) measured from click to first paint on the client And the UI remains interactive (no blocking overlays and inputs remain usable)
Progressive Detail Rendering After Primary Cause
Given the primary blocking cause has rendered When additional details (after‑hours party, applicable holiday, conflicting focus block) are available Then secondary details render incrementally within 500 ms of the primary cause And one‑click alternates render within 700 ms of the primary cause And only inline, non‑blocking loaders are shown for pending details
Debounce Rapid Repeat Clicks
Given the user clicks the same red high‑risk area repeatedly within a 300 ms window When those clicks are processed Then only one computation/network request is executed for that window And the UI renders a single explanation instance with no duplicates And a subsequent click after the debounce window triggers a fresh load
Stale Data Indicator and Refresh
Given any data used in the explanation exceeds its TTL (profiles/work hours: 24h; holidays: 7d; focus blocks: 5m) When the user clicks a red high‑risk area Then a “May be outdated” indicator is shown next to each stale element with an "As of" timestamp And a Refresh control is available and, when clicked, replaces stale elements with fresh data within 1.5 s or shows an inline error if refresh fails And the initial primary cause still renders within 300 ms despite staleness
Graceful Fallback on Cache Miss or Service Outage
Given cold caches or an upstream holiday/profile service is unavailable When the user clicks a red high‑risk area Then a minimal explanation renders within 300 ms that identifies unavailable elements and suggests a retry And a Retry action is present and does not block interaction; failures show inline error without modal dialogs And the degraded event is logged with a reason code for telemetry
Precompute and Cache Warm‑Up on Timeline Changes
Given the user modifies participants, acceptable hours, or the visible timeline window When the UI is idle for 200 ms Then conflict summaries for visible red areas are precomputed and cached, completing within 300 ms total on a standard device And participant profiles and holiday lookups are resident in memory before the next click (first‑click cache hit rate ≥90% over a 1‑hour window) And the first click after such changes still renders the primary cause within 200 ms (p95)
Analytics & Audit Trail for Decisions
"As a product analyst, I want visibility into which conflicts occur and which alternates users choose so that we can refine suggestions and policies based on real behavior."
Description

Track structured events for red‑zone views, cause expansions, alternate selections, and overrides (with anonymized participant counts and policy types). Store an audit trail on who overrode what rule and why, respecting privacy and regional data regulations (configurable retention, GDPR/CCPA controls). Provide basic dashboards or export to the existing analytics pipeline. Expected outcome: product and ops teams can measure feature adoption, identify common blockers, and improve policies over time.

Acceptance Criteria
Track Red‑Zone View Event
Given a user clicks a red area in Why Red and the details panel opens When the panel is fully rendered Then a single event named "red_zone_view" is emitted once per open with payload fields: event_id (UUIDv4), timestamp (UTC ISO‑8601), org_id, user_id_hash (SHA‑256), meeting_id (UUIDv4), red_zone_id, client_version, session_id, device, timezone_contexts ([]), anonymized_participant_count (int) And the event excludes PII such as participant emails, names, or calendar subjects And the event is queued and delivered to the analytics sink within 5 seconds 95th percentile with at‑least‑once delivery And duplicate emissions for the same panel open are deduplicated by event_id
Track Cause Expansion Event
Given the user expands a listed cause within a red area (e.g., After Hours, Holiday, Focus Block) When the cause accordion is opened Then an event "red_cause_expand" is emitted with payload: event_id, timestamp, org_id, user_id_hash, meeting_id, red_zone_id, cause_type (after_hours|holiday|focus_block), cause_id, policy_type, anonymized_affected_count (int) And if the user collapses and re‑expands within the same session, events include parent event correlation_id And no participant identifiers or titles are included And delivery meets 95th percentile under 5 seconds with retry/backoff on failure
Track Alternate Selection Event
Given the user clicks a one‑click alternate to resolve a red area When the alternate is applied to the meeting proposal Then an event "alternate_selected" is emitted with payload: event_id, timestamp, org_id, user_id_hash, meeting_id, original_slot (UTC), new_slot (UTC), total_shift_minutes (int), resolved_causes ([cause_type]), red_zone_id And total_shift_minutes equals absolute difference between original_slot and new_slot in minutes And the event is emitted exactly once per applied alternate and before navigation away And delivery and PII constraints match global analytics policies
Record Override Decision with Audit Trail
Given a user with permission attempts to schedule in a red zone by overriding a rule When the user submits a required justification text (min 10 chars) Then an immutable audit record is written with fields: audit_id (UUIDv4), timestamp, org_id, user_id_hash, user_role, meeting_id, rule_type (after_hours|holiday|focus_block|other), justification, affected_counts (anonymized), outcome (approved|denied) And justification is encrypted at rest and redacted in non‑admin views And the audit record is append‑only and cannot be edited; corrections require a new record referencing audit_id And a corresponding analytics event "override_committed" is emitted with non‑sensitive metadata
Privacy, Consent, and Retention Controls
Given an org admin configures data controls When retention is set (e.g., 30/90/365 days or custom) Then analytics events and audit records older than the threshold are purged within 24 hours of eligibility And data collection can be toggled off per org; when off, no new events are stored and UI indicates limited analytics And GDPR/CCPA requests (access/delete) for a user are fulfilled by locating user_id_hash and deleting/exporting associated records within 30 days And data residency is honored by storing records in the configured region(s) And privacy policy references and consent surfaces are logged via "privacy_setting_changed" events
Analytics Dashboards and Export Integration
Given product/ops users view the Analytics area When selecting a date range (last 7/30/90 days, custom) Then dashboards display: total red_zone_view events, top 5 blocker types, percent resolved by alternates, override rate, median shift minutes And widgets load within 2 seconds at 95th percentile for 90‑day ranges on orgs up to 10k events/day And users can export aggregated metrics as CSV and stream raw events to the existing analytics pipeline via configured destinations And exports include schema versioning and succeed or fail with clear status; failures are retried and surfaced to admins
Access Control and Redaction for Audit Logs
Given RBAC is configured When a non‑admin user opens an audit view Then they cannot see audit records; access is denied with a friendly message And when an auditor or admin opens an audit record, PII remains redacted except justification visible only to admins And all audit log views are themselves logged as "audit_viewed" with viewer role and audit_id And exports of audit logs respect redaction settings and require admin scope

Duration Sweep

Scrub a duration slider (15–120+ minutes) to watch safe windows stretch or shrink in real time. Avoid overruns that cross into after‑hours and quickly pick the longest viable window for your agenda.

Requirements

Real-time Safe Window Recalculation
"As a remote team lead, I want safe windows to update instantly as I change the meeting duration so that I can quickly see what fits everyone and avoid after-hours invites."
Description

Dynamically recompute and render cross-time-zone “safe windows” as the duration slider is adjusted, honoring each attendee’s working hours, holidays, and focus blocks. The client requests or derives precomputed availability intervals and intersects them, filtering out any start times whose end time would violate constraints for the selected duration. Visual updates must occur within 150 ms per interaction for up to 20 attendees across 8 time zones, with debounced input and incremental recalculation to maintain 60 fps animations. Handles DST transitions, overlapping constraints, and multi-day spans, and gracefully degrades on slow networks with a loading state and coarse updates. Integrates with the availability engine, timeline renderer, and conflict explanation service to keep UI, booking validation, and share links consistent.

Acceptance Criteria
Sub-150 ms Recalc on Duration Scrub (20 Attendees, 8 Time Zones)
Given up to 20 attendees across 8 time zones with precomputed availability loaded When the user scrubs the duration slider continuously for 5 seconds Then each visual update of safe windows completes within 150 ms of the most recent input event And the timeline animation maintains at least 60 fps with no more than 5% dropped frames And no main-thread task exceeds 8 ms per frame during the scrub
Correct Interval Intersection and Overrun Filtering
Given attendees’ working hours, holidays, and focus blocks are defined and active And a duration D is selected When safe windows are recalculated Then windows equal the intersection of all attendees’ available intervals And any start time whose end time (start + D) intersects a disallowed period for any attendee is excluded And overlapping constraints are treated as closed intervals without gaps And changing D monotonically shrinks or expands candidate windows accordingly (no phantom expansions) And the maximum duration position returns the longest viable continuous window present
DST Boundary Correctness
Given attendees in regions with differing DST change dates And the selected date range includes a DST transition When computing and rendering safe windows for duration D Then all times are computed in UTC internally and displayed in each attendee’s local wall time And no window is rendered that would end outside working hours due to the DST shift And windows on the transition day reflect the correct one-hour offset And share links and bookings encode the UTC start/end and reproduce identical windows on reopen
Debounced Input and Incremental Recalculation
Given the user rapidly adjusts the duration slider (>=10 changes/second) When recalculating safe windows Then input events are debounced so no more than 10 recomputations per second occur during the scrub And a final recomputation is triggered within 100 ms after input stops And incremental recomputation limits processing to intervals affected by the delta in D And the number of availability engine calls during a 5-second scrub is <= 60
Multi-day and Overnight Window Handling
Given duration D may cross midnight in one or more time zones When computing safe windows spanning multiple days Then only windows whose entire [start, start + D] lies within each attendee’s working hours across day boundaries are included And windows that would cross into after-hours or holidays for any attendee are excluded And the renderer segments windows by day without visual overlap or truncation And the earliest valid next-day window is presented when same-day options are invalid
Graceful Degradation on Slow or Offline Networks
Given network latency >500 ms or availability data missing When the user scrubs the duration slider Then a loading or “coarse windows” state appears within 100 ms And coarse windows use last-known cached data and are clearly labeled And final precise windows render within 150 ms of data arrival without freezing the UI And interactions remain responsive (input latency <50 ms) while data loads
Cross-Service Consistency and Conflict Explanations
Given a start time selected from a safe window for duration D When booking validation runs and a share link is generated Then booking validation accepts the selection without additional conflicts And the share link, when opened on another device, renders the same safe windows for D And disabled/filtered starts provide conflict explanations that identify the blocking attendee and constraint type And any change to D updates UI, validation, and share link parameters in lockstep
After-hours Overrun Guard & Snapping
"As a scheduler, I want the system to block and trim overrunning durations so that I don’t accidentally book outside someone’s work hours or focus blocks."
Description

Prevent selection of durations that would cause a proposed meeting to overrun into disallowed time for any participant. When an overrun is detected, visually indicate the overflow on the timeline and snap the selection to the longest valid subwindow within the same segment. Provide clear rationale (e.g., “Ends during Alice’s focus block”) via tooltip and inline messaging. Enforce the same validation at booking time to eliminate mismatches between preview and final scheduling. Supports host override policies and per-attendee guardrail visibility rules, and respects minimum/maximum duration limits set at the event type level.

Acceptance Criteria
Real-time duration sweep overrun guard with visual overlay and snapping
Given an available segment is visible and a duration slider (15–120+ minutes) is active When the user scrubs the duration so the meeting end would enter any participant’s disallowed time Then the overflow portion is visibly shaded on the timeline at the exact overflow span And the selection snaps to the longest valid subwindow within that segment And the duration control value updates to match the snapped subwindow length And the drag handle cannot extend past the snapped end within that segment And visual updates occur within 100 ms at the 95th percentile of interactions
Cross-timezone guardrails: after-hours, holidays, and focus blocks
Given multiple attendees in different time zones with configured working hours, holidays, and focus blocks When evaluating duration changes for potential overruns Then any overlap with any attendee’s disallowed time prevents that duration And the rationale cites the earliest-overlapping guardrail (by start time) and the attendee name And time-zone conversions are correct to the minute with no off-by-one minute errors And holidays are treated as fully disallowed unless an attendee has explicitly marked availability
Booking-time validation parity
Given a previewed time and duration have passed overrun guard checks (with any client-side snapping applied) When the host attempts to book Then server-side validation applies the same guardrail ruleset and version as the client And if validation passes, the booking is created without any overrun into disallowed time And if validation fails due to calendar changes, the booking is blocked, the new overflow is highlighted, and snapped alternatives within the same segment are offered And the blocking attendee and guardrail type are shown in the error message
Host override policy flow
Given host override is enabled for the event type When a selection would overrun a guardrail Then an "Override and book anyway" control is shown to the host along with the rationale And selecting override requires explicit confirmation and an optional note And the booking is created with an override flag and warning badges in host UI And invitees do not see other attendees’ private guardrail details per visibility rules
Per-attendee guardrail visibility and messaging
Given guardrail visibility rules are configured (host: full detail; attendees: redacted) When tooltips or inline messages are shown for an overrun Then the host sees attendee name and guardrail type (e.g., focus block, holiday, after-hours) And attendees see generic messaging without other attendees’ personal details And screen-reader text conveys the rationale with accessible labels And tooltips can be dismissed and re-opened via keyboard
Deterministic snapping and no-valid-subwindow handling
Given multiple valid subwindows exist within the same segment for a chosen start When snapping is required due to an impending overrun Then the system chooses the longest valid subwindow; if equal lengths exist, it chooses the earliest by start time And if no subwindow can satisfy the event type’s minimum duration, the selection is cleared, a "No valid time in this segment" message is shown, and booking is disabled And next-valid suggestions are displayed for upcoming segments when available
Event-type min/max duration limits enforcement
Given an event type with minimum and maximum duration limits When the user scrubs the duration slider Then the slider cannot be set below the minimum or above the maximum And snapping never increases duration above the maximum And if snapping would require reducing duration below the minimum, the selection is blocked and a message explains the constraint And the same duration limits are enforced during booking validation
Longest Viable Window Auto-Suggest
"As a meeting organizer, I want an automatic suggestion for the longest duration that works for everyone so that I can plan an agenda without trial and error."
Description

Compute and display the maximum duration that fits all constraints for the currently selected date range, offering a one-click chip (e.g., “Max: 95 min”). Present per-day maximums in the calendar scroller and update suggestions in real time as attendees, constraints, or duration settings change. When no common window exists, show an actionable zero-state with options to expand hours, relax constraints, or propose multiple shorter sessions. Expose this computation via a lightweight endpoint for server-rendered previews and share-link unfurls.

Acceptance Criteria
Compute and Display 'Max: X min' Chip
Given a selected date range and attendees with defined working hours, holidays, focus blocks, and time zones When the scheduling view loads or any input affecting availability changes Then the system computes the longest contiguous meeting duration within any single day in the range that satisfies all attendees' constraints And a chip labeled "Max: <minutes> min" is displayed with the correct value And the chip is shown only when the maximum is greater than 0 minutes
Per-Day Maximums in Calendar Scroller
Given the calendar scroller is visible for the selected date range When availability is computed Then each day cell displays its per-day maximum contiguous duration in minutes (e.g., "95m") or "0" if none And day badges update within 500 ms after any change to attendees, constraints, or date range And days with 0 are visually distinguished and have accessible labels indicating "No common window"
Real-Time Update Responsiveness and Consistency
Given the user adds/removes an attendee, edits work hours, toggles holidays or focus blocks, or adjusts the date range When the change is applied Then the "Max" chip and per-day maximums recalculate and update within 500 ms (P95) without a page refresh And displayed values reflect the last applied change (no stale intermediate results) And a loading state is shown if recomputation exceeds 250 ms
Zero-State With Recovery Options When No Common Window Exists
Given there is no common window on any day within the selected date range When availability is computed Then the "Max" chip is hidden and a zero-state message is displayed And the zero-state presents options: "Expand Hours", "Relax Constraints", and "Propose Multiple Sessions" And selecting "Expand Hours" opens the hours editor pre-populated for affected attendees And selecting "Relax Constraints" opens the constraints panel with relevant toggles pre-selected And selecting "Propose Multiple Sessions" opens a planner suggesting multiple shorter sessions that fit constraints
Duration Slider Integration and One-Click Max Selection
Given the "Max: <minutes> min" chip is visible When the user clicks the chip Then the duration slider value is set to the suggested maximum, capped at the slider's upper bound if necessary And the earliest matching window for that duration is highlighted on the timeline And the selection respects all attendees' constraints and the selected date range
Lightweight API Endpoint for Max Duration and Unfurls
Given a request to the max-window endpoint with valid attendees, constraints, time zones, and date range parameters When the request is processed Then the response status is 200 with JSON including overall_max_minutes, per_day_max_minutes, and an earliest_window sample And the endpoint returns results consistent with the UI computation for the same inputs And P95 latency is ≤ 300 ms for up to 10 attendees and a 14-day range And invalid input returns 400 with error details; transient errors return 503 with a retry-after header And responses include appropriate CORS and caching headers for share-link unfurls and do not expose PII beyond provided inputs
Duration Slider & Timeline Integration
"As a user, I want a clear duration slider that directly updates the timeline so that I can see how meeting length impacts availability at a glance."
Description

Implement a responsive duration control with presets (15, 30, 45, 60, 90, 120) and custom input up to at least 240 minutes, supporting mouse, touch, and wheel interactions. The slider drives the timeline visualization, stretching/shrinking safe-window overlays and updating counts of viable start times. Persist the last-used duration per event type and encode it in the URL state to support refresh and share flows. Provide smooth animations, clear numeric labels, and step granularity of 5 minutes, with guardrails for min/max duration defined at the template level. Internationalization-ready formatting and theming are required for consistent look and feel across the app.

Acceptance Criteria
Select Preset Duration Updates Safe Windows and Start Count
Given the timeline is visible with safe‑window overlays and a viable start count display When the user clicks the 90‑minute preset on the duration slider Then the safe‑window overlays stretch/shrink to reflect 90‑minute windows within 150 ms of selection And the viable start count recalculates to the number of 5‑minute start ticks whose end time stays within all participants’ allowed hours and excludes holidays and focus blocks And the duration label displays the localized form of 1h 30m And the slider handle snaps exactly to the 90‑minute step
Custom Duration Input With Template Guardrails and 5‑Minute Granularity
Given a meeting template defines a minimum duration of 20 minutes and a maximum of 180 minutes When the user types 17 minutes into the custom input Then the value snaps to the nearest 5‑minute increment within bounds (20 minutes) And an inline message indicates the applied minimum constraint When the user types 187 minutes Then the value snaps to 180 minutes and the slider handle updates accordingly And any value entered or adjusted is rounded to the nearest 5 minutes (0–2 min down, 3–4 min up) on blur and on release
Mouse, Touch, and Wheel Interactions Adjust Duration Reliably
Given the duration slider has focus or hover When the user scrolls the mouse wheel one notch up or down Then the duration increases or decreases by exactly 5 minutes per wheel notch and stays within template bounds When the user drags the slider handle with a mouse or touch Then the duration changes continuously and snaps to 5‑minute steps on release And the handle is draggable via touch with no more than 30 ms input latency on modern mobile devices And keyboard focus and pointer capture are maintained during drag until release
Duration State Persists Per Event Type and in Shareable URL
Given an event type A with last‑used duration 60 minutes and event type B with last‑used duration 30 minutes When the user opens event type A Then the slider initializes to 60 minutes and the timeline reflects 60‑minute windows When the user changes A to 90 minutes Then a URL parameter updates to encode 90 minutes within 300 ms without a full page reload And refreshing the page or opening the shared URL restores 90 minutes for that view And opening event type B still initializes to its own last‑used 30 minutes And if the encoded duration is out of bounds for the current template, it clamps to the nearest bound and the UI reflects the clamped value
Responsive Scrubbing Animations and Label Readability
Given the user scrubs the duration slider continuously When the handle moves Then overlay transitions animate smoothly at a median 60 fps with no more than 5% frames exceeding 32 ms And numeric duration labels update no more than every 50 ms during scrubbing (throttled) and finalize the exact value on release And animation duration for settle/ease does not exceed 250 ms and does not block input And labels remain legible at all breakpoints with no overlap or truncation of units
After‑Hours Overrun Prevention in Window Calculation
Given participants’ work hours, holidays, and focus blocks are configured When the user increases the duration so that a previously viable start would overrun after hours or a blocked period Then that start is removed from the viable count and the safe‑window overlay visually shrinks to exclude the overrun range And the longest remaining viable window is still selectable without crossing constraints And no start time is presented as viable if its end time exceeds any participant’s allowed window
Internationalization‑Ready Formatting and Theming Compliance
Given the app locale is set to en‑US When the slider shows 90 minutes Then the label reads “1h 30m” using ICU/unit formatting and theme tokens for typography and color When the app locale is set to fr‑FR Then the same value renders as “1 h 30 min” with correct spacing and pluralization And all slider colors, spacings, and typography derive from theme variables without hardcoded values And high‑contrast theme preserves minimum 4.5:1 contrast for labels and handle indicators
Accessibility & Keyboard Controls
"As a keyboard and screen reader user, I want accessible controls and announcements for duration changes so that I can schedule effectively without barriers."
Description

Ensure the Duration Sweep experience is fully accessible: ARIA-compliant slider role with labelled value, keyboard increments (arrow keys for step, Shift+arrow for 15-minute jumps), and a polite live region announcing the new duration and count of viable windows after each change. Provide sufficient color contrast for safe/unsafe states, focus rings, and a non-color indicator for overrun. Support screen readers on desktop and mobile, reduced-motion preferences, and haptic feedback where available. Meets WCAG 2.2 AA requirements and includes automated and manual accessibility testing.

Acceptance Criteria
ARIA Slider Semantics and Labeling
Given the Duration Sweep control is rendered When inspected by assistive technologies Then it exposes role "slider" And has an accessible name that includes the word "Duration" And conveys min value of 15 minutes And conveys max value of at least 120 minutes (or higher as configured) And conveys the current value including units (e.g., "45 minutes") And exposes a step increment of 5 minutes
Keyboard Increments and Focus Visibility
Given the Duration Sweep slider has keyboard focus When the user presses ArrowRight or ArrowUp Then the duration increases by 5 minutes without exceeding the max When the user presses ArrowLeft or ArrowDown Then the duration decreases by 5 minutes without going below the min When the user presses Shift+ArrowRight or Shift+ArrowUp Then the duration increases by 15 minutes When the user presses Shift+ArrowLeft or Shift+ArrowDown Then the duration decreases by 15 minutes And no page scroll occurs from arrow key usage And a visible focus ring of at least 2px with ≥3:1 contrast is present at all times while focused
Polite Live Region Announcements
Given an aria-live region labeled for duration updates exists When the duration value changes via keyboard, mouse, or touch Then within 300ms the live region announces "Duration: <X> minutes. Viable windows: <N>." And the live region uses aria-live="polite" and aria-atomic="true" And rapid successive changes are coalesced to no more than 1 announcement per 200ms And duplicate announcements of the same value are suppressed
Color Contrast and Non-Color Overrun Indicator
Given safe and unsafe windows are visualized on the timeline Then all text labels meet ≥4.5:1 contrast against their background And all non-text UI components (including timeline bars, handles, focus indicators) meet ≥3:1 contrast When a duration causes a window to overrun outside working hours Then an icon and/or pattern fill indicates "Overrun" in addition to color And the overrun indicator has an accessible name/description (e.g., aria-label="Overrun") And overrun windows are not selectable via keyboard or pointer
Cross-Platform Screen Reader Operability
Given the feature is tested on NVDA (Firefox), JAWS (Chrome), VoiceOver (macOS Safari), VoiceOver (iOS Safari), and TalkBack (Android Chrome) When a user navigates to the Duration Sweep slider Then the screen reader announces name, role, current value, min, max, and step And the user can adjust the value using standard slider commands/gestures And live region announcements are heard once per value change without duplication And no critical/blocking SR issues are observed across all listed environments
Reduced Motion and Haptic Feedback
Given the OS preference prefers-reduced-motion is enabled When the user changes the duration Then no animated stretch/shrink transitions play; updates are instantaneous And all information is still conveyed via text/ARIA without loss Given the device supports haptic feedback and reduced-motion is disabled When the duration crosses into or out of an overrun state Then a single short haptic pulse (≤30ms) is emitted per boundary crossing And the user can disable haptics in app settings
WCAG 2.2 AA Conformance and Accessibility QA
Given a build candidate of the Duration Sweep feature When automated scans (axe-core, Lighthouse accessibility) are executed Then there are 0 Critical or Serious violations And all contrast checks meet required ratios When manual audits are performed Then the feature meets WCAG 2.2 AA for 2.1.1 Keyboard, 2.4.7 Focus Visible, 1.3.1 Info and Relationships, 4.1.2 Name Role Value, 1.4.3 Contrast (Minimum), 1.4.11 Non-text Contrast, 2.5.7 Dragging Movements, and 2.3.3 Animation from Interactions And test results and remediation evidence are documented and linked in the story
Smart Link Duration Encoding
"As a host, I want to share a link that pre-sets the desired duration so that invitees land in the right context and can adjust within my boundaries."
Description

Allow creators to pre-set and constrain duration via smart links by encoding default, min, and max duration parameters. Opening a link initializes the slider and timeline to those values while still honoring attendee guardrails and host policies. The link preview reflects the pre-set duration in titles and metadata. Changes made by recipients remain within host-defined bounds and are persisted in the URL for collaborative iteration. Backwards-compatible with existing links and validated server-side to prevent out-of-range bookings.

Acceptance Criteria
Initialize Duration Slider and Timeline from Smart Link
Given a smart link containing default duration D, minimum M, and maximum X When the link is opened Then the duration slider initializes to clamp(D, M, X) And the slider's selectable range is M to X And safe windows render for the initialized duration, honoring host policies and attendee guardrails (work hours, holidays, focus blocks)
Recipient Adjusts Duration Within Host-Defined Bounds
Given a smart link session with bounds M..X When the recipient adjusts the duration via slider or direct input Then the value cannot be set lower than M or higher than X And attempts to exceed bounds are prevented and clamped to the nearest bound And adjustments re-compute safe windows immediately
Duration Persistence in URL for Collaboration
Given the recipient changes duration to D' within bounds When the page is refreshed or the URL is shared and opened on another device/browser Then the session initializes with duration D' and the same bounds And the URL contains encoded duration parameters representing D' and bounds And the same safe windows are shown for D' without requiring server session state
Link Preview and Metadata Reflect Pre-set Duration
Given a smart link with default duration D When the link is rendered in previews (Open Graph/Twitter cards, in-app listings) Then the title and metadata include the formatted duration (e.g., "D minutes") And updating D in the URL updates the preview title/metadata on next render And if duration params are invalid, the preview falls back to product defaults without errors
Backwards Compatibility with Existing Links
Given a legacy smart link without duration parameters When the link is opened Then the slider initializes to the product default duration and full allowable range And the timeline renders as it did prior to this feature And bookings proceed successfully with no change in behavior And duration parameters are only added to the URL if the recipient changes the duration
Server-side Validation Blocks Out-of-Range Bookings
Given a booking request whose duration is outside host-defined bounds or violates attendee guardrails for the chosen time When the booking API is called Then the server returns a 4xx validation error with a machine-readable code indicating the violation And no calendar event is created And the client displays the validation message and keeps the user on the scheduling view
Sanitization of Conflicting or Malformed Duration Parameters
Given a smart link with malformed parameters (non-numeric values, negative numbers, min > max, or default outside [M, X]) When the link is opened Then non-numeric values are ignored, negatives are treated as 0, and min/max are corrected so that min ≤ max And the default duration is clamped within the corrected range And the session loads without error and records a sanitization analytics event

Surge Levels

Preset intensity modes (Light, Standard, Critical) that expand windows by a defined width and duration, set the required approver tier, and cap after‑hours exposure. One click applies a right‑sized surge that matches incident severity or launch phase—speeding decisions while preventing overreach.

Requirements

Surge Level Configuration & Governance
"As a workspace admin, I want to define and govern preset surge levels with guardrails so that teams can act quickly without violating our scheduling and after-hours policies."
Description

Provide an admin-configurable catalog of preset surge modes (Light, Standard, Critical) with parameters for window expansion (absolute hours and/or percentage), activation duration/TTL, required approver tier(s), after-hours exposure caps (per user/team/time period), scope limits (teams/calendars/regions), and required justification notes. Include defaults, cloning, versioning, validation, and environment-based policies (workspace, org). Enforce RBAC so only permitted roles can create or modify modes; support enterprise policy lock to prevent local overrides. Persist configurations per workspace with auditability and backward compatibility for existing links and schedules.

Acceptance Criteria
RBAC Enforcement on Surge Mode Management
Given a user without a permitted role (Org Admin, Workspace Admin, Policy Manager) When they attempt to create, edit, clone, delete, publish, or deprecate a surge mode via UI or API Then the action is denied with HTTP 403 and error code RBAC_SURGE_DENY And an audit log entry is recorded with actor, action, target mode/version, timestamp, and workspace/org IDs Given a user with a permitted role When they perform any of the above actions Then the action succeeds And the audit log captures before/after values and correlation ID
Create Surge Mode With Validation
Given an admin on workspace W When they create a surge mode with a unique name (case-insensitive) and provide: window_expansion (abs_hours >= 0 and/or percent between 0–100), ttl_minutes in [5..10080], non-empty approver_tiers in ascending order, after_hours_caps with non-negative values and defined reset periods (day/week/month), scope (teams/calendars/regions), and justification_required flag Then the mode is saved as version v1 with status Draft and all fields persisted Given invalid inputs (e.g., percent > 100, abs_hours < 0, ttl_minutes outside range, duplicate name within W, empty approver_tiers, negative caps) When save is attempted Then save is blocked with field-level errors and error code VALIDATION_FAILED; no record is created Given optional fields are omitted When defaults exist (per preset type) Then documented defaults are applied and stored (e.g., Standard: approver_tiers=["Manager"], ttl_minutes=120)
Clone and Versioning with Backward Compatibility
Given an existing mode "Critical" v1 When an admin clones it or edits and publishes changes Then a new immutable version v2 is created with auto-incremented version number And v1 remains available for existing schedules/links that reference v1 Given a schedule link referencing v1 When v2 is published Then the link continues to apply v1 until explicitly repointed by the owner And repointing is a deliberate action captured in audit logs Given a version is deprecated When users attempt to select it for new use Then it is hidden/blocked for new selection but does not alter existing references
Enterprise Policy Lock Prevents Local Overrides
Given org-level policy lock is ON with constraints (e.g., max_window_expansion_percent=30, min_required_approver_tier=Director) When a workspace admin attempts to save a surge mode that violates any constraint Then the save is blocked with HTTP 409 and error code POLICY_LOCK_VIOLATION listing violated rules Given policy lock is ON When workspace attempts to modify org-managed fields via API/UI Then the controls are disabled/ignored and an audit entry records the attempted override Given policy lock is OFF When a workspace defines stricter local limits than the org baseline Then the save succeeds and the stricter limits apply within the workspace
After-Hours Exposure Caps Enforcement
Given after_hours_caps are configured per user (e.g., 2h/day) and per team (e.g., 6h/week) When surge mode is active and scheduling is attempted that would exceed any cap Then the scheduling action is blocked with error AFTER_HOURS_CAP_REACHED and message showing remaining allowance Given a user with override permission and required approver tier attempts the same action When they provide justification Then the override succeeds and the event is logged with justification and approver Given the reset boundary occurs (local day/week per ISO-8601) When the period rolls over Then counters reset automatically and are visible in reporting/API
Activation TTL, Approvals, and Justification
Given a surge mode requires approver tiers ["Manager","Director"] When a user requests activation without both approvals in order Then the request remains Pending and cannot take effect Given the final approval occurs at time T0 When ttl_minutes=180 is configured Then activation begins at T0 and auto-expires at T0+180 minutes with windows reverting and notifications sent Given justification_required=true When activation is requested without a justification note of at least 20 characters Then submission is blocked with error JUSTIFICATION_REQUIRED
Scope Limits, Persistence, and Auditability
Given a surge mode is scoped to teams [Growth,SRE], calendars [ops@,incidents@], and regions [AMER,EMEA] When a user outside the scope attempts to apply it to a link or schedule Then selection is unavailable or returns HTTP 403 with error SCOPE_DENY Given an eligible user applies the mode within scope When smart links are generated Then surge windows are only offered to in-scope entities; out-of-scope invitees do not see expanded windows Given configurations are saved When API GET /workspaces/{id}/surge-modes is called Then records are returned including scope, version, policy lock status, and timestamps Given any create/update/delete/publish/deprecate/activate action occurs When the action completes Then an immutable audit record is written with actor, before/after delta, timestamp, source IP, and correlation ID
One-Click Surge Toggle on Timeline
"As a team lead scheduling an urgent meeting, I want to apply a surge with one click so that acceptable windows expand instantly across time zones."
Description

Add a prominent, accessible UI control to apply a selected surge level to the scheduling timeline. On toggle, recompute and visually overlay expanded meeting windows across participants’ time zones in real time, showing a before/after diff and any policy impacts (approvals needed, after-hours budget usage). Support undo/redo, keyboard shortcuts, mobile responsiveness, and low-latency rendering. Persist the chosen surge level into the session and any generated scheduling links, with clear badges and tooltips explaining the active mode and expiry.

Acceptance Criteria
One-Click Surge Toggle Applies Selected Level
- Given a loaded timeline with at least two participants in distinct time zones and a surge level preselected (Light, Standard, or Critical), When the user activates the Surge toggle via click or Space/Enter, Then the selected surge parameters are applied to the timeline immediately without page reload. - Then the control reflects its on/off state and the active level with a visible label/badge. - And the control is accessible: focusable via Tab order, has role="switch" with an aria-label announcing the current level, supports Enter/Space activation, and meets WCAG 2.1 AA contrast (≥4.5:1).
Before/After Diff and Policy Impact Display
- Given the baseline timeline is visible, When a surge level is applied, Then an overlay displays expanded meeting windows for all participants with a legend indicating "Before" vs "After". - And the overlay quantifies the change for each window (added minutes and extended range) and updates live if the level changes. - And policy impacts are shown inline: required approver tier, remaining after-hours budget impact, and any blocks when caps would be exceeded. - And if the after-hours cap would be exceeded, Then the scheduling action is disabled and a callout explains the approval needed to proceed.
Undo/Redo of Surge Changes
- Given one or more surge level changes have occurred, When the user invokes Undo (Ctrl/Cmd+Z or Undo UI), Then the previous surge state is restored (including overlay, approvals, and badges) within 100ms. - When the user invokes Redo (Ctrl/Cmd+Shift+Z or Redo UI), Then the reverted surge state is re-applied. - And the history retains at least the last 20 surge-related actions per session and is cleared if participants or working hours configuration change, with a non-blocking notice.
Keyboard Shortcuts for Surge Toggle and Levels
- Given the app has focus and no input field is active, When the user presses 1, 2, or 3, Then the surge level switches to Light, Standard, or Critical respectively and applies to the timeline. - When the user presses S, Then the Surge toggle toggles on/off using the last selected level. - And keyboard shortcuts are listed in the Surge tooltip/help, and do not conflict with browser defaults on major browsers.
Mobile Responsive Surge Control and Overlay
- Given a viewport width between 320px and 767px, When the user taps the Surge control, Then the surge applies with a single tap and the overlay renders without horizontal scrolling of the control. - And the control remains reachable (visible without overlap), hit target ≥44x44px, and tooltips are presented as touch popovers with dismiss actions. - And pinch/zoom and timeline scroll interactions remain responsive (no dropped frames > 1 in 60) while the overlay is visible.
Low-Latency Recompute and Rendering
- Given a schedule with up to 20 participants across up to 8 time zones, When a surge is toggled on or its level is changed, Then recomputation and rendering of the overlay complete within 150ms median and 250ms p95 on a mid-tier device and network. - And interactions (scroll/drag on the timeline) maintain ≥55 FPS while the overlay is present.
Persistence in Session and Shared Links with Badges and Tooltips
- Given a surge level is active, When the user navigates between pages or refreshes within the session, Then the active level and expiry persist and are restored on load. - When the user generates a scheduling link, Then the link encodes the active surge level and expiry; recipients opening the link see the same overlay and an "Active Surge: [Level]" badge. - And the badge includes a tooltip that explains the mode and displays the expiry time to the minute; upon expiry the surge automatically disables, the badge updates to "Expired", and the timeline reverts to baseline.
Approval Workflow & Tiered Authorization
"As an approver, I want to review and authorize critical surges so that we balance urgency with protection of after-hours policies."
Description

Implement tiered approval rules mapped from the selected surge level, supporting single- and multi-step approvals, fallback approvers, SLAs/timeouts, and reminders via in-app, email, and chat integrations. Block or queue surge effects until required approvals are granted, with clear user feedback and status tracking. Provide emergency break-glass with post-facto review and justification. Integrate with SSO/SCIM roles and groups for dynamic approver assignment. Log all decisions and reasons for auditability.

Acceptance Criteria
Surge level maps to approver tiers and rules
Given surge levels (Light, Standard, Critical) are configured with approver tiers and SLAs, When a requester selects a surge level, Then the UI immediately displays the exact approver sequence, required approver tier(s), and SLA per tier. Given a selected surge level, When the requester submits the surge, Then the approval request is instantiated with the mapped tiers and approver candidates derived from configuration. Given tier mappings are updated by an admin, When a new surge request is created, Then it uses the latest mapping; And existing in‑flight requests retain their original mapping with a visible version tag. Given a selected surge level, When the system validates approver eligibility, Then only eligible, active users for the specified tiers are listed as approvers.
Single- and multi-step approvals behavior and rejection
Given a multi-tier approval chain, When Tier 1 approves, Then Tier 2 becomes actionable; And the request status reflects Tier 2 Pending. Given any tier rejects, When the rejection is submitted with a reason, Then the request status becomes Rejected; And downstream tiers are canceled; And the requester is notified with the stated reason. Given a single-tier chain, When the designated approver approves, Then the request becomes Approved; And no additional approvals are required. Given a pending request is edited in a way that changes surge level or affected window width/duration, When the requester resubmits, Then prior approvals are invalidated; And the approval flow restarts using the updated mapping; And approvers see a change summary.
SLA timeouts with reminders and fallback escalation
Given a tier with an SLA duration and reminder cadence configured, When a request enters that tier, Then initial notifications are sent via in-app, email, and chat; And reminders are sent at the configured cadence until a decision or timeout. Given the SLA elapses without a decision, When timeout occurs, Then the system escalates to the configured fallback approver or next tier; And sends escalation notifications across channels; And logs a timeout event. Given an approver attempts to act after a timeout escalation, When they submit a decision, Then the action is declined with a message indicating escalation has occurred; And the late attempt is logged. Given an approver acts before timeout, When approval or rejection is recorded, Then all pending reminders for that tier are canceled; And the next step proceeds per outcome.
Blocking and queuing surge effects until approval with user feedback and status tracking
Given a surge request is submitted, When approvals are pending, Then no surge-induced availability changes are applied; And external recipients continue to see pre-surge windows. Given approvals are pending, When the requester views the request, Then the UI shows a Pending Approval banner including current tier, approver, elapsed time, and SLA remaining. Given the final required approval is recorded, When the system applies the surge, Then surge effects are applied atomically within the configured apply window (default 60s); And the request status becomes Active with an applied timestamp. Given any tier rejects, When rejection is recorded, Then the surge is not applied; And queued changes are discarded; And the requester receives a rejection notification including the stated reason. Given the requester withdraws a pending request, When withdrawal is confirmed, Then the approval flow is canceled; And no surge effects are applied; And the status becomes Withdrawn.
Break-glass emergency override with post-facto review
Given a user with break-glass permission, When they invoke break-glass on a surge, Then they must provide a non-empty justification and acknowledge a policy notice before proceeding. Given break-glass is invoked, When confirmation is submitted, Then surge effects apply immediately; And the request status becomes Break-Glass Active; And post-facto review tasks are created for required approvers with a due time per policy. Given post-facto review is in progress, When any approver rejects, Then the request is marked Non-Compliant; And compliance owners are notified; And the surge remains active unless a policy flag Enforce Auto-Rollback is enabled. Given break-glass is disabled by policy or the user lacks permission, When they attempt to invoke it, Then the action is blocked with a clear policy error. Given break-glass was used, When viewing audit details, Then justification, actor, timestamps, and notification records are present.
SSO/SCIM-driven dynamic approver assignment and deprovisioning
Given SSO/SCIM role and group mappings exist for approver tiers, When a surge request is created, Then candidate approvers for each tier are resolved from current directory data at creation time. Given directory changes occur (role removal, deprovision, suspend) while a request is pending, When the system syncs, Then ineligible users are removed from candidacy; And assigned approvers who become ineligible are replaced per fallback rules; And a note is logged on the request. Given SCIM sync runs, When group membership changes, Then new requests reflect the latest membership within the configured sync interval; And in-flight requests keep their current approver unless they become ineligible.
Comprehensive audit logging and export
Given any approval lifecycle event occurs (submit, approve, reject, edit, timeout, escalation, fallback, break-glass, apply, withdraw), When the event is committed, Then an immutable audit entry is written containing timestamp, actor, actor source (UI/API/email/chat), surge ID, surge level, tier, action, outcome, reason/justification, SLA metrics, and state transitions. Given audit logs exist, When an admin filters by date range, actor, surge level, outcome, or tier, Then results are returned according to the reporting SLA for the dataset size; And counts reflect the filtered result set. Given audit logs are exported, When an admin requests CSV or JSON, Then the export includes all visible fields and ordering; And large exports are delivered asynchronously with a downloadable link and email notification. Given an audit entry with notifications, When the entry is viewed, Then associated notification delivery statuses (in-app/email/chat) and timestamps are displayed.
Surge Duration, Scheduling & Auto-Revert
"As a program manager, I want surges to automatically start and end on schedule so that policies revert without manual cleanup."
Description

Allow users to set a start time and fixed duration or expiry for each surge application, including scheduling surges in advance (e.g., launch windows). Display countdowns and send pre-expiry notifications. Automatically revert all affected timelines, links, and policies to baseline at expiry, ensuring no lingering expanded windows. Support recurring surges with calendar-aware exceptions (holidays, blackout dates). Handle edge cases across time zones and daylight saving changes with deterministic state transitions.

Acceptance Criteria
Schedule One-Time Surge With Fixed Duration
Given no active surge and a selected surge level And the user selects a start time and either a fixed duration or an explicit expiry in the chosen time zone When the user saves the surge Then the system stores precise UTC start and end instants and displays the computed end time in the user's local time And the surge automatically activates at the start instant and shows a live countdown of remaining time while active And the surge is visible in the schedule view as a single window covering the active range
Send Pre-Expiry Notifications
Given an active surge with pre-expiry notification lead times configured (e.g., 60m, 5m) When current time equals expiry minus each configured lead time Then a notification is sent once per lead time to the defined recipients and channels And the notification includes the exact expiry timestamp and remaining duration in the recipient's local time And no duplicate or late notifications are sent
Auto-Revert All Affected Assets At Expiry
Given an active surge that has expanded windows, updated smart links, and temporary policies When the surge reaches its expiry instant Then all timelines, smart links, and policies revert to the baseline configuration within 60 seconds And new invitations outside baseline hours are blocked immediately after expiry And any caches are invalidated so that external viewers see baseline behavior within 60 seconds And the revert operation is idempotent so repeated processing produces the same baseline state
Recurring Surges With Calendar-Aware Exceptions
Given a recurring surge rule (e.g., weekdays 08:00–10:00 anchored to the owner's time zone) and organization holiday/blackout calendars When an occurrence falls on a holiday or blackout date Then that occurrence is skipped and no surge is active for that date And generated future occurrences reflect updates to the holiday/blackout calendars within 15 minutes And recurrence instances generate and activate at their scheduled times with correct countdowns and pre-expiry notifications
Deterministic Behavior Across Time Zones and DST
Given a scheduled surge anchored to the owner's time zone and participants distributed across multiple time zones When a DST forward jump makes the chosen local start time non-existent Then the start is moved to the next valid local minute and the end is computed by adding the fixed duration in wall-clock time When a DST fallback duplicates a local time Then the first occurrence of the duplicated time is used as the start and the end is computed by adding the fixed duration in wall-clock time And all clients display the same absolute start/end instants translated to their local times, and state transitions occur exactly once
Scheduled Surge Auto-Activation And Conflict Handling
Given a future-dated surge is scheduled for a workspace that currently has no overlapping surges When the start instant arrives Then the surge becomes active without user action and the countdown begins immediately And attempts to manually apply an overlapping surge for the same scope during the active window are blocked with a clear error And editing the scheduled surge before it starts updates the stored times and any related notifications accordingly
After-Hours Exposure Cap & Budgeting
"As an operations lead, I want to cap and monitor after-hours exposure during surges so that we protect team wellbeing while still moving quickly."
Description

Track and enforce configurable after-hours exposure budgets during surges at person, team, and workspace levels per time period (daily/weekly/monthly). Provide both soft warnings and hard caps, with real-time metering that predicts impact before applying a surge. Account for focus blocks, holidays, and individual work-hour profiles when calculating exposure. Offer fair distribution strategies (rotation/weighting) to prevent repeatedly burdening the same individuals. Prevent scheduling beyond remaining budget and suggest alternative windows or participants.

Acceptance Criteria
Real-Time Exposure Prediction Before Applying a Surge
Given person, team, and workspace budgets are configured for daily, weekly, and monthly periods And a surge level is selected with a candidate meeting window and participants When the scheduler adjusts the window, participants, or surge level Then predicted after-hours exposure minutes are displayed per participant, per team, and for the workspace for each applicable period And remaining budget and percent utilization are shown for each level And indicators show whether soft warnings or hard caps would be triggered And the prediction updates within 500 ms of any change
Soft Warning Thresholds During Scheduling
Given soft warning thresholds (e.g., 80%) are configured per period and level And predicted exposure for the selected window remains below the hard cap When predicted utilization for any level meets or exceeds its soft threshold Then a non-blocking warning is displayed listing impacted levels, periods, predicted utilization, and remaining budget And scheduling remains allowed And the user must acknowledge the warning before finalizing And an audit event is recorded with user, time, levels, periods, predicted utilization, and surge level
Hard Cap Enforcement Blocks Scheduling
Given hard caps are configured per period and level When predicted exposure exceeds remaining budget at any level for any period Then the Schedule action is disabled and a blocking error names the level(s), period(s), and required reduction in minutes And no event is created or updated And an audit event is recorded including blocking details and user action
Multi-Level Budget Debiting and Roll-Up
Given a meeting under a surge is successfully scheduled When the meeting is created Then after-hours minutes per participant are debited from their personal budgets for each affected period And team budgets for each participant’s team are debited by that participant’s after-hours minutes And the workspace budget is debited by the sum of all participants’ after-hours minutes And budgets never drop below zero; any operation that would do so is blocked And reschedules or cancellations adjust all affected budgets within 10 seconds and log the adjustments
Accurate Exposure Calculation with Profiles, Focus Blocks, and Holidays
Given each participant has a work-hours profile, focus blocks, a holiday calendar, and a time zone When exposure is predicted or debited for a candidate window Then only minutes outside the participant’s work hours or within a focus block or holiday are counted as after-hours for that participant And minutes overlapping multiple constraints are counted once And minutes within work hours and not in a focus block or holiday are not counted And daylight saving transitions are correctly handled per participant’s time zone
Fair Distribution via Rotation and Weighting
Given a configured fairness strategy with rotation and optional participant weights When the system proposes participant sets for a surge-requiring meeting Then the selection minimizes the maximum projected utilization among eligible participants while respecting weights And no individual’s projected monthly exposure exceeds the team average by more than the configured fairness threshold unless no feasible set exists And over a series, no person is assigned twice before all eligible members have one assignment, subject to constraints And the UI shows the rationale and projected utilization for the chosen set
Alternative Windows or Participants When Caps Block Scheduling
Given scheduling is blocked by a hard cap for the selected window and participants When the user requests suggestions Then at least 3 alternative time windows within the next 5 business days that meet all budgets and constraints are returned, ranked by least additional exposure And if no windows are feasible, alternative participant sets or lower-intensity surge levels that satisfy budgets are suggested And each suggestion lists predicted exposure by level and period, remaining budgets, and fairness impact And the user can apply any suggestion in one action
Audit Trail, Reporting & Analytics
"As a compliance officer, I want a detailed audit and reports of surge usage so that we can demonstrate adherence to policy and identify misuse."
Description

Create immutable event logs for surge configuration changes, applications, approvals, overrides, and resulting after-hours exposure. Provide dashboards and exports (CSV/JSON) summarizing usage by team, level, time period, and outcome, with filters and drill-down. Generate alerts for anomalies (e.g., repeated Critical surges, frequent break-glass use). Respect data retention and privacy settings, with RBAC for access to sensitive records. Expose aggregated metrics via API for BI tools.

Acceptance Criteria
Immutable Surge Event Logging
- Given a user creates, updates, applies, approves, overrides, or cancels a Surge Level, When the action is saved, Then an audit event is recorded with fields: event_id (UUID), timestamp_utc (ISO-8601), actor_id, actor_role, team_id, surge_level, action_type, approver_tier, prior_state, new_state, correlation_id. - Given a Surge Level is applied, When the resulting window overlaps outside team work hours, Then after_hours_minutes is calculated and stored on the event. - Given an audit event is recorded, When integrity is verified, Then a SHA-256 hash chain validates previous_hash + payload, and events are append-only (no updates/deletes via API/UI). - Given a request to modify or delete an audit event, When attempted via API or UI, Then the operation is rejected with 405 and no change in event count. - Given data retention N days, When an event exceeds N days, Then it is purged by a system job and a purge summary record is logged.
Dashboard Summaries with Filters and Drill-down
- Given events exist across teams, surge levels, time periods, and outcomes, When filters Team=A, Level=Critical, Period=Last 30 days are applied, Then displayed totals (event count, unique incidents, total after_hours_minutes) equal the underlying matched events (variance = 0). - Given a summary tile or chart segment is clicked, When drill-down opens, Then it lists the exact contributing events with pagination and each row links to a full event detail view including hash and payload. - Given filters that match zero events, When applied, Then the dashboard shows zero values and an empty state with no errors.
CSV/JSON Export Accuracy and Performance
- Given filters are applied, When exporting CSV or JSON, Then only matching events are included with columns: event_id, timestamp_utc, team_id, surge_level, action_type, actor_role, approver_tier, outcome, after_hours_minutes; column order and field names conform to contract v1.2. - Given an export <=100000 rows, When requested, Then the file is generated within 30 seconds at p95 and is available via signed URL for 7 days. - Given timestamps and numerics, When exported, Then timestamps are UTC ISO-8601 (CSV) or RFC3339 (JSON) and numeric fields are non-null (0 if absent). - Given role-based masking rules apply, When a restricted role exports, Then masked fields appear as REDACTED (CSV) or null (JSON).
Anomaly Detection Alerts for Surge Misuse
- Given CriticalSurgeThreshold=3 per 7 days per team, When a team exceeds the threshold, Then an alert is created within 5 minutes, delivered to email and Slack, and an alert event with alert_id is logged. - Given BreakGlassThreshold=2 per 30 days per user, When exceeded, Then an alert appears in the Alerts dashboard with status Open and can be acknowledged to In Progress or Resolved. - Given multiple breaches within 60 minutes, When alerts would be generated, Then they are deduplicated into a single alert with an incremented occurrence_count. - Given delivery failures, When notifying, Then retries use exponential backoff up to 24 hours and final state is DeliveryFailed if undelivered.
RBAC and Privacy Controls for Sensitive Records
- Given role=Owner or Admin, When accessing audit dashboards, exports, or APIs, Then all permitted fields are visible; Given role=Member, Then sensitive fields (e.g., actor_email, notes) are masked or omitted per policy. - Given an unauthorized role requests sensitive audit endpoints, When called, Then API returns 403 and UI navigations are hidden/disabled. - Given retention=365 days and legal hold flags, When the retention job runs, Then records older than 365 days without legal_hold=true are purged and a purge report is available to Owners. - Given any audit record is viewed, When accessed, Then an access log entry (viewer_id, timestamp_utc, event_id) is recorded and retained 90 days.
Aggregated Metrics API for BI Tools
- Given a valid OAuth2 token with scope metrics.read, When calling GET /api/v1/metrics/surge?team_id=T&bucket=day&from=F&to=To, Then response 200 returns time buckets with fields: count_events, count_critical, approvals_required, after_hours_minutes_sum, and pagination cursor when applicable. - Given the dashboard shows metrics for Team=T and Period=P, When the API is called with equivalent filters, Then the values match exactly with <=5 minutes staleness. - Given client exceeds 60 requests/minute per token, When rate limit is hit, Then API responds 429 with Retry-After header. - Given invalid params or scopes, When called, Then API returns appropriate errors: 400 invalid_parameter, 401 invalid_token, 403 insufficient_scope.
Smart Link & API Integration
"As a project lead, I want shared scheduling links and APIs to carry surge context so that invitees and tools see the correct expanded windows without manual coordination."
Description

Ensure generated scheduling links encode the selected surge level, scope, and expiry securely, degrading gracefully when expired or unapproved. Provide API endpoints and webhooks to programmatically apply, query, and subscribe to surge state changes, enabling integrations with incident management and release tooling. Synchronize surge context across calendar providers so invitees see accurate expanded windows. Enforce token scoping and idempotency to prevent overreach when links are forwarded or reused.

Acceptance Criteria
Smart Link Encodes Surge Context and Handles Expiry/Approval
Given a user selects a surge level (Light, Standard, Critical) and generates a smart link for a scoped schedule When the link is opened by an authenticated in-scope recipient before expiry Then the rendered windows expand per the surge's width/duration, enforce the after-hours cap, and display the required approver tier Given a valid smart link is generated When the server validates its token Then the token contains signed, tamper-evident claims (surgeLevel, scopeId(s), expiryTs, approverTier, nonce) and no raw claims appear outside the opaque token Given the link has expired When it is opened Then the UI shows an "Expired" state, hides expanded windows, falls back to default availability, and the API returns HTTP 410 Given the surge has not been approved yet for a level requiring approval When the link is opened Then the UI shows "Approval required" and no expanded windows are applied; the API returns HTTP 403 to surge-context requests Given the token is tampered or the signature is invalid When validation runs Then the request is rejected with HTTP 401 and no surge context is applied
Idempotent API to Apply, Query, and Update Surge State
Given an integrator POSTs to /api/v1/surges with a valid scoped access token and an Idempotency-Key header When the request is unique Then the API returns 201 with surgeId, surgeLevel, scope, status, and expiryTs Given the same payload and Idempotency-Key are retried within 24 hours When the API receives the request Then it returns 200 with the original surgeId and an Idempotent-Replay: true header without creating a duplicate Given a client GETs /api/v1/surges?scope=...&status=active Then only surges within the token's scope are listed Given a client PATCHes /api/v1/surges/{id} to approve or reject When the caller lacks the required approver tier Then the API returns 403 and leaves the surge status unchanged Given a client PATCHes /api/v1/surges/{id} to update expiry within policy bounds Then the API persists the change and emits a corresponding webhook event
Webhooks Deliver Signed Surge Events with Reliable Retries
Given a webhook endpoint is registered with a shared secret When surge.applied, surge.updated, surge.expired, surge.approval_required, surge.approved, or surge.rejected occurs Then an event is delivered within 60 seconds containing eventId, type, createdAt, scope, surgeId, and an HMAC-SHA256 signature header over the raw payload Given the receiver verifies the signature using the shared secret and event payload Then valid events are accepted; invalid signatures are rejected with 400 and retried Given delivery fails (non-2xx) When retries execute Then exponential backoff is used for up to 24 hours with at least 10 attempts; delivery is at-least-once and each event includes a stable idempotency key for de-duplication
Calendar Providers Show Accurate Surge-Expanded Windows
Given a surge is applied to a schedule synced to Google Calendar, Microsoft 365, and ICS When the surge is active Then invitees viewing smart links or calendar overlays see expanded windows reflecting the surge within 5 minutes Given the after-hours cap defined by the surge level When windows are expanded Then the expansion never exceeds the configured cap across all providers Given the surge expires or is rejected When propagation completes Then expanded windows revert to baseline within 5 minutes and any "[Surge: <Level>]" annotation is removed Given an ICS feed is used Then X-Timeglue-Surge fields convey surgeLevel, scope, and expiry without exposing secrets or tokens
Token Scoping Prevents Overreach for Forwarded or Reused Links
Given a smart link is generated with scope (orgId/teamId/projectId) and audience (email domain or user list) When the link is opened by an out-of-scope or unauthenticated recipient Then only baseline availability is shown, surge context is hidden, and the API responds 403 to surge-context requests Given a smart link token is exchanged for a session When the same token is reused Then the server returns 409 Already Used and requires in-scope re-authentication Given the link is forwarded inside scope When an in-scope authenticated user opens it Then surge context is applied but no permissions are escalated; applying or approving changes still requires the appropriate role Given the token exceeds its TTL (24 hours or less) When it is used Then the server rejects it and logs the attempt for audit
Approver Tier Enforcement by Surge Level
Given surge level mappings (Light=Tier-0/no approval, Standard=Tier-1, Critical=Tier-2) When a surge requiring approval is applied via API or smart link Then the surge remains in pending_approval until a user with the required tier approves Given a user without the required approver tier attempts to approve Then the request is rejected with 403 and an audit record is created with userId, tier, scope, and timestamp Given a surge is approved When applied Then the approver identity, tier, timestamp, and scope are recorded and emitted in a webhook; the expanded windows reflect the level's width/duration and after-hours cap Given the approver tier is downgraded or revoked after approval When future approvals are attempted Then they fail; existing approvals remain traceable and cannot be overridden without a higher-tier approver

Blast Radius Map

Preview the human impact of a surge before you flip it on. See projected after‑hours minutes, legal risk, and fairness effects by person, team, and region—plus suggested start/end times that minimize harm. Make fast, defensible calls with clear, humane tradeoffs.

Requirements

Surge Parameters & Constraint Inputs
"As a remote team lead, I want to define and adjust surge parameters and constraints in one place so that the impact preview accurately reflects our team’s reality."
Description

Provide a unified UI and API to define surge parameters (start/end time candidates, duration, affected teams/projects, and regions) and ingest constraints from Timeglue’s existing data (work hours, holidays, focus blocks, PTO, and time zone preferences). Allow painting acceptable hours on a draggable timeline, apply role-based criticality tags, and attach jurisdiction metadata per person for regional compliance checks. Validate inputs (e.g., overlap with blackout periods), surface defaults from team profiles, and recalculate previews in real time as parameters change. Ensure all inputs are time zone–aware, versioned, and auditable to support defensible decision making.

Acceptance Criteria
Create and Save Surge Parameters via UI and API
- Given a team profile with defaults, when a planner opens New Surge, then default duration, affected teams/projects, and regions are pre-filled and editable. - Given the surge form, when the user adds 1–10 start/end time candidates and a duration, then the system validates end after start and duration ≤ window and saves all candidates with ISO 8601 timestamps including time zone offsets. - Given the API v1, when POST /surges includes start_time_candidates, end_time_candidates, duration_minutes, team_ids/project_ids/region_codes, then the system returns 201 with surge_id and version_id, and subsequent GET returns identical values. - Given invalid inputs (missing duration, invalid region code, overlapping/duplicate candidates), when the user attempts to save, then the save is blocked and specific inline error messages are shown for each field.
Auto-Ingest Participant Constraints into Surge Plan
- Given a surge scope with participants across regions, when the surge draft is opened, then work hours, holidays, focus blocks, PTO, and time zone preferences for each participant are fetched and applied to the plan. - When constraint data is missing or stale (>24h), then the participant is flagged as Constraints Incomplete with a details tooltip and a link to refresh or edit profile. - Then all ingested constraints are normalized to each participant’s local time and the plan’s reference time zone without loss of fidelity. - Given the API, when GET /surges/{id}?include=constraints, then per-participant constraint summaries are returned.
Paint Acceptable Hours on Draggable Timeline
- Given the timeline, when the planner drags to paint acceptable hours, then segments snap to 15-minute increments, support drag-resize/move, and can be removed with a single action. - When a painted segment overlaps a holiday, PTO, or focus block for any affected participant, then the overlapped portions are visually highlighted and saving the plan is blocked with a conflict list identifying participants and times. - When the plan is reloaded, then all painted segments persist exactly with original start/end in their respective time zones. - Given keyboard users, when tab/arrow/enter are used, then painting and adjustments are possible without a mouse.
Assign Role-Based Criticality Tags
- Given the participants list, when a planner assigns criticality tags, then only allowed values (Must-Have, Nice-to-Have, Optional, On-Call) can be selected and at least one Must-Have participant is required to finalize. - When tags are changed, then the change is persisted immediately, included in subsequent GET/API responses, and logged in the audit trail with actor and timestamp. - When invalid tags are submitted via API, then the request is rejected with 400 and a descriptive error.
Attach Jurisdiction Metadata for Regional Compliance
- Given a participant, when jurisdiction metadata (country_code, subdivision_code) is added or updated, then values are validated against ISO 3166-1 alpha-2 and ISO 3166-2 and persisted. - When any required jurisdiction field is missing or invalid for any Must-Have participant, then Finalize Surge is disabled and a compliance banner lists those participants. - Given the API, when GET/POST/PUT are used, then jurisdiction metadata is accepted and returned for each participant in the surge scope.
Real-Time Preview Recalculation on Parameter Changes
- Given an open surge draft, when any surge parameter or painted segment is created, updated, or deleted, then the blast radius preview recalculates and updates visible metrics within 2 seconds for cohorts up to 200 participants. - When recalculation exceeds 2 seconds, then a progress indicator is shown until completion and the last-updated timestamp is refreshed on completion. - Then no stale values remain; all visible metrics, conflict counts, and suggested windows are consistent with the latest inputs. - When the user undoes a change, then the preview reverts accordingly within the same performance bounds.
Versioning and Audit Trail for Surge Parameters
- Given any change to surge parameters (time candidates, duration, scope, painted hours, criticality tags, jurisdiction), then a new immutable version is created with version_id, actor, timestamp, and a diff of changed fields. - Given GET /surges/{id}/versions, then the API returns an ordered list of versions; and GET /surges/{id}?version={version_id} returns the exact snapshot for that version. - When Revert to Version is invoked, then parameters are restored to that snapshot, a new version is created noting the revert, and the UI refreshes to reflect restored values. - All stored timestamps in versions and audit entries include time zone offsets and are stored in UTC internally.
Impact Modeling Engine
"As a team lead, I want to see projected after-hours minutes by person, team, and region so that I can understand who will be affected and by how much."
Description

Compute projected human impact for a proposed surge, including after-hours minutes, count of impacted days, focus-block collisions, and PTO conflicts per person, aggregated to team and region. Respect each individual’s working hours, holidays, focus blocks, and time zone. Produce explainable outputs with per-metric breakdowns and deltas versus baseline schedules. Support scalable batch computation for large orgs, with caching and incremental recomputation on parameter changes to keep the preview responsive.

Acceptance Criteria
After‑Hours Minutes Accuracy
Given a proposed surge window and each user’s working hours and time zone When the engine computes after-hours minutes Then it returns the exact number of minutes that fall strictly outside that user’s working hours within the surge window at 1‑minute precision And minutes spanning midnight or DST transitions are computed in the user’s local time correctly And the returned value is 0 for users fully within working hours during the surge window
Impacted Days Count Accuracy
Given a proposed surge window that may span multiple local calendar days for each user When the engine computes impacted days per person Then it returns the count of distinct local dates on which the surge contributes any non‑zero impact metric (after‑hours minutes, focus‑block collisions, or PTO conflicts) And local date boundaries are determined in each user’s time zone And holidays that remove all working time for a day only count as impacted if any impact metric is non‑zero on that date
Focus‑Block and PTO Conflict Detection
Given users with configured focus blocks and approved PTO events in their calendars When the engine evaluates the proposed surge window Then it returns per‑person totals of minutes overlapping focus blocks (within working hours) and minutes overlapping PTO events And overlapping intervals within the same category (e.g., overlapping focus blocks) are de‑duplicated so minutes are not double‑counted And interval boundary handling is inclusive of start and exclusive of end times
Calendar Constraints and Time‑Zone Respect
Given per‑user working hours (including split shifts), holidays, and time zones with possible DST changes When the engine computes overlaps for all impact metrics Then all comparisons are performed in the user’s local time with correct DST transitions And split working hours per day are honored (e.g., 09:00–12:00 and 13:00–17:00) And organization‑recognized holidays are treated as non‑working time unless overridden by explicit working hours
Explainable Outputs and Baseline Deltas
Given a baseline schedule and a proposed surge window When the engine produces results Then each metric per person includes baseline, proposed, and delta values with delta = proposed − baseline And each person includes a breakdown of contributing intervals with local start/end timestamps, metric type {after_hours|focus_block|pto}, and source identifiers And recomputing totals from the breakdown exactly matches the reported totals (tolerance = 0 minutes) And metrics are defined to be non‑overlapping across categories to avoid double‑counting
Team and Region Aggregations Correctness
Given per‑person metrics and an org directory mapping each person to a canonical team and region When the engine aggregates results Then team and region totals equal the sum of their members’ per‑person metrics for each metric type And people missing team or region are grouped under Unassigned without breaking totals And changing a person’s team or region in the input is reflected in aggregates on the next computation
Scalable Batch, Caching, and Incremental Recomputation Performance
Given an org of up to 5,000 users and a surge window of up to 14 days When computing from a cold start Then p95 computation time completes in ≤ 3,000 ms and results match a full reference recomputation exactly Given identical inputs repeated When results are served from cache Then p95 latency is ≤ 200 ms and outputs are bit‑for‑bit identical to a full recompute Given only surge start/end shift by ≤ 60 minutes When performing incremental recomputation Then ≤ 15% of users are recomputed and p95 latency is ≤ 500 ms for 5,000 users And cache invalidation occurs only for inputs affected by parameter changes (participants, calendars, working hours, holidays, time zones)
Legal Risk Scoring & Compliance Checks
"As an operations manager, I want clear compliance flags and risk levels by region so that I can avoid creating unlawful or noncompliant schedules."
Description

Evaluate surge scenarios against configurable policy rules and region-specific constraints (e.g., daily/weekly hour caps, minimum rest windows, night work restrictions, overtime thresholds). Generate a risk score and explicit flags per person, team, and region with human-readable explanations and rule references. Allow organization-level policy overrides and thresholds, and expose a summary widget indicating highest-risk geographies. Record which rules triggered for auditability and provide guidance on parameter adjustments that would resolve violations.

Acceptance Criteria
Region-Specific Rule Evaluation and Flagging
Given an organization with regions US-CA, DE, and IN configured with daily/weekly hour caps, minimum rest windows, night work restrictions, and overtime thresholds And a surge scenario scheduled across those regions impacting 50+ people When the compliance evaluation runs Then for each person, team, and region the system emits zero or more flags per violated rule including: rule_id, rule_name, severity, start_time, end_time, affected_minutes, and delta_from_limit And entities with no violations are explicitly marked Compliant with zero flags And the evaluation completes within 2 seconds for 1,000 entities and within 10 seconds for 10,000 entities under standard environment settings And against the baseline test dataset, no false-positive flags are produced and all expected violations are detected
Organization-Level Policy Overrides and Thresholds
Given region defaults are set (e.g., weekly_hours_cap=40, min_rest=11h) And an organization-level override is configured (e.g., weekly_hours_cap=45, min_rest=10h) When a surge is evaluated Then precedence uses the organization override over region defaults for all affected entities And toggling the override off reverts evaluations to region defaults on the next run And the UI and API surface the effective value and its source (override or region) for each applied rule And the audit record captures the override identifiers and values used during the evaluation
Risk Score Calculation and Normalization
Given rule severities and weights are configured and published And a surge evaluation completes with one or more violations When risk scores are computed Then each person, team, region, and the overall scenario receive a numeric risk_score between 0 and 100 And each score is labeled with a risk_level band: Low (0–33), Medium (34–66), High (67–100) And a score_breakdown lists the top 3 contributing rules with their percentage contributions And repeated runs with identical inputs produce identical risk_score values And resolving any violation never increases the risk_score for the affected entity or overall scenario And the API and UI return the same risk_score and risk_level values for the same evaluation_id
Human-Readable Explanations with Rule References
Given violations are detected When flags are presented Then each flag includes an explanation_text in plain language describing the breach and magnitude (e.g., "+2h over weekly cap") And each flag includes rule_reference with code and source (e.g., "OrgPolicy HR-01"; "EU WTD Art.6") And each flag links to a policy URL or document section where available And explanation_text is localized to the viewer’s locale when translations exist, otherwise falls back to en-US And explanation_text length does not exceed 300 characters
Audit Log of Triggered Rules and Config Snapshot
Given a surge evaluation is executed When results are stored Then an immutable audit record is created containing: evaluation_id, scenario_id, timestamp, actor_id, config_version, rule_set_snapshot, overrides_applied, triggered_rules per entity, and all computed risk_scores And audit records are retrievable by evaluation_id and filterable by date range and region And audit records can be exported as CSV and JSON And records are retained for at least 12 months And any attempt to alter an existing record is blocked; a new versioned record must be created and linked, preserving history
Summary Widget for Highest-Risk Geographies
Given a completed evaluation exists When the Blast Radius Map summary widget renders Then it displays the top 5 geographies by risk_score with color-coded risk_level badges And it highlights the single highest-risk geography And selecting a geography drills down to its region view showing flags, counts, and scores And the widget updates within 1 second after policy or schedule adjustments are applied and re-evaluated And the widget meets WCAG 2.1 AA for contrast, keyboard navigation, and has descriptive aria-labels
Guidance on Parameter Adjustments to Resolve Violations
Given one or more violations are present When the user requests guidance Then for each violation the system returns at least one actionable adjustment specifying the parameter (e.g., start_time, end_time, surge_length, staffing), the exact delta, and the projected change in risk_score and flag count And a simulate action applies selected adjustments in a sandbox and recomputes compliance without persisting changes And accepting a suggestion applies changes, persists the updated scenario, and triggers an automatic re-evaluation And if no compliant adjustment exists within configured bounds, the system returns a clear message with the limiting constraint identified
Fairness Index & Load Distribution
"As a department head, I want a fairness index and distribution view so that I can make equitable decisions across regions and teams."
Description

Quantify fairness impact across teams and regions by measuring distribution of after-hours load (e.g., variance and Gini-like index) and highlighting overburdened groups. Exclude sensitive personal attributes; compute only on organizational and geographic dimensions. Provide configurable weights (e.g., prioritize minimizing repeated burden on the same region) and show historical rotation context to prevent recurrent overload. Surface actionable insights that indicate which parameter changes would improve fairness without significantly increasing overall impact.

Acceptance Criteria
Compute Fairness Indices by Team and Region
Given a surge plan with defined work hours, holidays, focus blocks, and proposed meeting windows When fairness calculations are executed Then the system computes, for each team and region: total after-hours minutes, per-capita after-hours minutes, variance, coefficient of variation, and a Gini-like index And indices are rounded to two decimals and displayed alongside team/region names And if total after-hours minutes = 0 for a cohort, all indices return 0 And calculations complete within 5 seconds for plans up to 10,000 participants And the API returns a calculation timestamp and algorithm version
Exclude Sensitive Attributes from Fairness Calculations
Given user and schedule datasets containing organizational (team, department), geographic (region, time zone), and sensitive personal attributes (e.g., gender, age, ethnicity, disability) When the fairness service ingests inputs and computes metrics Then only organizational and geographic fields are used; sensitive attributes are ignored and not required And the computation API schema does not accept sensitive attributes And generated outputs, logs, and exports contain no sensitive attributes And automated tests verify absence of sensitive fields in payloads and logs
Highlight Overburdened Groups
Given computed fairness metrics by team and region And default overburden thresholds set to: per-capita after-hours > median + 50% OR absolute after-hours > 60 minutes/week (both configurable) When results are rendered Then groups breaching thresholds are flagged with an Overburdened badge and sorted to the top And each flagged group shows a tooltip with: value vs median, rank percentile, and delta to next-best group And clicking a flagged group opens a detail panel listing top affected individuals (anonymized counts) and time windows causing the load
Configurable Weights for Fairness Optimization
Given user-adjustable weights for (a) penalizing repeated burden on the same region and (b) penalizing dispersion (variance) When a user changes either weight or toggles “limit total impact change” (default ±5%) Then suggested start/end windows recompute within 800 ms and update predicted fairness indices and total after-hours minutes And increasing the repetition-penalty weight does not increase the repeated-burden metric for the top-burdened region (monotonicity) And if “limit total impact change” is on, total after-hours minutes of the suggested window remains within ±5% of the current plan And the chosen weights persist in the shareable link and reload identically
Historical Rotation Context and Recurrent Overload Prevention
Given access to the last 12 weeks of scheduled after-hours minutes per region (configurable 4–26 weeks) When computing fairness impact for a new surge plan Then the UI displays each region’s cumulative after-hours minutes over the lookback window and its rotation position (e.g., 3/8) And a region is marked Recently Overloaded if it was in the top quartile for ≥3 of the last 6 weeks And if any Recently Overloaded region would be top 2 in the new plan, the system proposes an alternative window that reduces that region’s projected after-hours minutes by ≥20% if feasible, else shows an Unavoidable badge with rationale
Actionable Parameter Change Suggestions
Given a current plan whose overall fairness index exceeds the target threshold (default 0.30, configurable) When the user opens suggestions Then the system generates up to three parameter changes (e.g., shift window ±30–60 minutes, adjust repetition weight, narrow/expand window) with predicted deltas for fairness indices and total after-hours minutes And each suggestion respects the user’s total impact guardrail (default ≤10% increase) unless the guardrail is explicitly relaxed And applying a suggestion updates the plan and displays a before/after comparison; realized metrics are within ±2% of the prediction And users can revert to the prior plan in one click
Deterministic, Auditable Calculations and Export
Given identical inputs and algorithm version When fairness calculations are re-run Then outputs (indices, flags, suggestions) are identical within numerical rounding tolerance And the user can export inputs, parameters (weights, thresholds), and results as CSV and JSON And the export includes calculation timestamp, algorithm version, and time zone normalization details And all timestamps are normalized to the plan’s canonical time zone in exports and UI
Harm-Minimizing Time Suggestions
"As a team lead, I want suggested surge windows that minimize harm so that I can make a quick, defensible decision."
Description

Run a fast optimization that searches candidate start/end windows and proposes the top three options that minimize a weighted objective (after-hours minutes, legal risk, fairness imbalance), subject to hard constraints (blackouts, work-hour caps). Present side-by-side comparisons with clear metric deltas versus the current selection. Allow tuning of weights per organization or scenario and provide constraint-aware reasoning (e.g., why a seemingly better window is infeasible). Ensure results return within seconds for interactive use.

Acceptance Criteria
Generate Top Three Harm-Minimizing Time Windows
Given an active surge scenario with selected participants, a target date range, and org-configured hard constraints (blackouts, public holidays, and per-person work-hour caps) When the user clicks "Get Suggestions" Then the system evaluates candidate start/end windows at a 15-minute granularity within the date range and returns 1–3 feasible suggestions And each suggestion is a contiguous window with explicit start and end timestamps in ISO 8601 (UTC) and duration in minutes And no returned suggestion overlaps any participant’s blackout or public holiday or causes that participant to exceed their configured work-hour caps And after-hours minutes are computed per participant using their local timezone, working hours, and daylight saving rules
Rank Suggestions by Weighted Objective
Given weights wa, wl, wf in [0,1] that sum to 1 When suggestions are computed Then each suggestion has a normalized objective score S = wa*A + wl*L + wf*F where A (after-hours), L (legal risk), and F (fairness imbalance) are each normalized to [0,1] And suggestions are sorted ascending by S; the top 3 are returned And ties (|ΔS| ≤ 0.005) are broken by lower A, then earlier start time And S, A, L, and F are displayed for each suggestion
Display Side-by-Side Metrics and Deltas
Given a current selection window exists When suggestions are shown Then each suggestion displays: total after-hours minutes, impacted people count, max after-hours for any person, legal risk (0–100), and fairness imbalance (e.g., standard deviation of after-hours minutes) And each metric shows a delta versus the current selection with sign (+/−) and units; improvements are highlighted in green and regressions in red And if no current selection exists, deltas show as N/A with neutral styling And metric values match the underlying calculations to within rounding rules (no more than 1 unit discrepancy)
Adjustable and Persisted Weight Configuration
Given an org admin updates default weights for after-hours, legal risk, and fairness When they save Then values are validated (each ≥ 0, not all zero), normalized to sum to 1, persisted at org scope, and become defaults for new scenarios And a scenario owner can override weights for the current scenario; overrides persist with the scenario and supersede org defaults And the UI shows the effective weights, their source (Org Default or Scenario Override), and last modified user and timestamp And changing weights re-ranks suggestions and updates scores within 1 second without a full page reload
Explain Infeasible Windows Due to Constraints
Given the optimizer excludes candidate windows When the user opens the reasoning panel Then for any excluded window the system lists violated hard constraints with details: constraint type, impacted user count, and up to 3 example users with local times and overage minutes And for each returned suggestion, a "Why not earlier/later?" action reveals the first blocking hard constraint in the adjacent earlier or later time slice And the API includes a reasons[] array for top excluded candidates with machine-readable codes (e.g., BLACKOUT_OVERLAP, CAP_EXCEEDED) and human-readable messages
Fast Suggestion Response Time SLA
Given up to 100 participants across up to 12 time zones and a 14-day date range When the user requests suggestions Then server-side compute latency is p95 ≤ 2.0 s and p99 ≤ 3.5 s; client render adds ≤ 500 ms p95 And with 300 participants across up to 30 time zones and a 30-day date range, latency is p95 ≤ 5.0 s and p99 ≤ 7.5 s And if computation exceeds 5 s, a non-blocking progress indicator appears; if it exceeds 8 s, the user is prompted to narrow scope without losing context
No Feasible Suggestions Handling
Given no candidate windows satisfy all hard constraints When the user requests suggestions Then the system returns zero suggestions with the message: "No feasible windows within the selected range under current hard constraints." And the message lists the top 3 blocking constraints (aggregated by impacted users) and offers next steps (e.g., expand date range, adjust caps) with deep links to permitted settings And the API responds 200 with suggestions[] = [] and a non-empty reasonsSummary[]
Interactive Blast Radius Map & Timelines
"As a decision-maker, I want an interactive map and timelines showing the blast radius so that I can quickly grasp who is affected and why."
Description

Visualize impact on a world map with region-level aggregation and color scales selectable by metric (after-hours minutes, risk score, fairness deviation). Include per-region timeline strips and person/team drilldowns with tooltips showing exact metrics and conflicts. Provide filters (teams, roles, regions, metrics), keyboard navigation, high-contrast themes, and screen-reader labels for accessibility. Maintain smooth performance for up to thousands of users via progressive loading and viewport-based rendering. Support snapshotting the current view for share and export.

Acceptance Criteria
Metric Selection and Color Scale on World Map
Given the map is loaded with the default metric (After‑hours Minutes) and a defined surge window When the user selects a different metric (Risk Score or Fairness Deviation) from the Metric selector or legend Then visible region colors update to reflect the selected metric within 300 ms And the legend title, units, thresholds, and color palette update to match the metric And tooltips display values for the selected metric with correct units and 1-decimal precision And color palettes are color‑blind safe; in High Contrast mode an alternate compliant palette is used And an ARIA live region announces “Metric set to <metric>” And no client errors are logged during the changeover
Region Aggregation and Tooltip Metrics
Given regions aggregate underlying team/person data by the active metric and surge window When the user hovers a region with a mouse or focuses it via keyboard Then a tooltip appears within 150 ms anchored to the region/focus outline And it shows: Region name; Teams count; People count; After‑hours Minutes (sum); Risk Score (0–100); Fairness Deviation (±%); Conflicts count And metric values are computed using regional work hours, holidays, and focus blocks And values use 1-decimal precision and correct units per metric And the tooltip remains while focused and dismisses on Escape/blur And equivalent information is exposed to screen readers via aria-describedby without duplicate announcements
Per-Region Timeline Strips and Drilldowns
Given a region is selected from the map When the region details panel opens Then a timeline strip renders using the region’s local time (24h) showing painted acceptable hours, surge overlay, and projected after‑hours minutes per hour And conflict markers are visible; hover/focus reveals person/team, local timestamp, and conflict type And Teams and People tabs list affected entities with sortable columns (Name, Role, After‑hours Minutes, Conflicts) And selecting a person opens a detail view with a breadcrumb; using Back returns focus to the previously focused list row And initial panel content renders within 500 ms; lists paginate or virtualize in 50‑item pages And drilldown data loads progressively without blocking map interactions
Filtering by Teams, Roles, Regions, and Metrics
Given filter controls for Teams, Roles, Regions, and Metrics When the user applies one or more filters Then map, timelines, counts, and drilldowns reflect the filtered dataset within 400 ms for in‑viewport elements And active filters show as removable chips; Clear All resets to defaults And the URL querystring encodes filters, selected metric, surge window, and viewport so a reload reproduces state And zero‑result states display a helpful message and a Reset action And filters are additive and persist across drilldowns and snapshots
Keyboard Navigation and Accessibility Compliance
Given a user navigates with keyboard and/or a screen reader When interacting with the Blast Radius Map, filters, timelines, and panels Then all interactive elements are reachable via Tab/Shift+Tab with visible focus indicators (>=3:1 contrast) And Arrow keys move focus between regions; Enter selects; Escape closes panels; “?” opens a shortcuts dialog And all controls and regions have accessible names/roles and labels include metric and units And dynamic updates (metric changes, filter changes, selection) are announced via polite ARIA live regions And a High Contrast theme can be toggled and respects system prefers-contrast; text contrast >=4.5:1; UI components >=3:1 And there are no keyboard traps and target sizes are >=24x24 px, meeting WCAG 2.2 AA
Progressive Loading and Viewport-Based Performance
Given a dataset up to 5,000 users across 300 regions When the user loads the map and performs common interactions (pan, zoom, filter, open panel) Then initial in‑viewport render completes within 2,000 ms on a mid‑tier laptop (p95) And p95 pan/zoom frame rate is >=45 FPS; p99 main‑thread long tasks <=100 ms during interactions And input latency p95 <=100 ms while dragging/panning And only in‑viewport tiles/labels render; off‑screen items are deferred; data loads in pages/streams And stabilized memory usage remains <250 MB for the tab And CPU drops to idle (<10%) within 2 s after interactions stop
Snapshot Share Link and Export
Given a user has configured filters, selected a metric, defined a surge window, set a viewport, and optionally opened a region drilldown When the user clicks Snapshot Then a shareable URL is generated that, when opened by an authorized user, restores the same state (filters, metric, surge window, viewport, selection) within 2,000 ms And PNG export captures the current viewport at 2x device pixel ratio including legend, timestamp, and scale; file size <=5 MB And CSV export includes rows for regions (in‑view by default, or all if toggled) with columns: region_id, region_name, metric, value, people_count, teams_count, conflicts_count, time_window_start, time_window_end And exports respect permissions: person‑level or PII data omitted for users without rights And a progress indicator is shown; failures present a retriable error without freezing the UI
Scenario Save, Share, and Audit Export
"As a program manager, I want to save and share scenarios and export a defensible report so that stakeholders can review and we can document the decision."
Description

Enable saving named scenarios capturing parameters, selected suggestion, and full metric outputs. Generate permissioned smart links for read-only previews with optional expiration. Provide exports (PDF/CSV) that include chosen window, tradeoff rationale, metrics by person/team/region, compliance flags, and fairness index, along with a changelog of parameter edits. Integrate with Timeglue’s decision records to keep a traceable history and make reviews easy for stakeholders and leadership.

Acceptance Criteria
Save Scenario Snapshot with Parameters, Suggestion, Metrics, and Rationale
Given I have configured a Blast Radius Map with parameters (regions, work hours, holidays, focus blocks, fairness/legal settings) and selected a suggested window When I click "Save Scenario" and enter a unique name (1–100 characters) Then the system saves a versioned snapshot that includes all parameters, the selected start/end window, full metric outputs (after-hours minutes per person/team/region, compliance flags, legal risk score, fairness index), and optional tradeoff rationale text And Then the scenario appears in the Scenarios list with name, owner, created timestamp, and version ID And Then the snapshot is immutable and metrics will not be recomputed unless I explicitly choose Recalculate And Then the save completes within 2 seconds for scenarios up to 5,000 people And If the name is not unique within the workspace, Then the save is rejected with a clear error and the existing scenario is not modified
Load Scenario Restores Exact State Without Recalculation
Given a saved scenario version exists When I open the scenario from the Scenarios list Then the Blast Radius Map restores the exact saved parameters, selected start/end window, and metric values as of the snapshot And Then the UI indicates "Snapshot as of <timestamp>" with the version ID And Then no background recomputation occurs until I click Recalculate And Then after I choose Recalculate and save, a new version is created without altering the original version
Permissioned Read-Only Smart Link with Expiration and Access Logging
Given a saved scenario version When I generate a smart link and select an audience (specific users/teams or workspace-wide) and optionally set an expiration date/time Then the system creates a unique, version-bound URL that enforces read-only mode And Then only authorized viewers can access the link; unauthorized or expired requests are denied with an explanatory message And Then the link automatically expires at the configured time and can be manually revoked by the owner at any time And Then all link opens are logged with viewer identity (if authenticated) or invitee email and timestamp, visible in the scenario's access log And Then the preview disables editing, saving, recalculation, and export actions
Parameter Edit Changelog Capture and Immutability
Given a saved scenario exists When any parameter is changed and I save a new version Then the changelog for the new version records field name, previous value, new value, editor, and timestamp for each changed parameter And Then changelog entries for prior versions remain immutable and cannot be altered And Then the full changelog is viewable in scenario details and included in exports
PDF Export Completeness and Fidelity
Given a saved scenario version When I export to PDF Then the PDF includes scenario name, version ID, created timestamp, owner, chosen start/end window, tradeoff rationale, metrics by person/team/region, compliance flags, legal risk score, fairness index, and the parameter edit changelog And Then all timestamps indicate source time zone and UTC equivalents And Then totals and subtotals match the UI values with zero variance And Then the export completes within 10 seconds for scenarios up to 5,000 people And Then the resulting document is readable on A4 and Letter page sizes without truncated tables or illegible text
CSV Export Schema and Data Integrity
Given a saved scenario version When I export to CSV Then the export includes records for people, teams, and regions containing: id, name, region, after_hours_minutes, compliance_flags, legal_risk_score, fairness_index And Then the export includes a changelog CSV containing: version_id, field, old_value, new_value, editor, timestamp And Then numeric fields are numeric, timestamps are ISO 8601 in UTC, encoding is UTF-8, and CSV follows RFC 4180 quoting And Then the number of people/team/region records matches the counts shown in the UI And Then the export completes within 10 seconds for scenarios up to 5,000 people
Decision Record Integration and Version Traceability
Given a saved scenario version When I attach it to an existing decision record or create a decision record from the scenario Then the decision record stores a reference to the scenario version and displays a summary of the chosen window, rationale, and key metrics And Then the scenario detail shows a backlink to the decision record And Then when I save a new scenario version, I can update the decision record to reference the new version while preserving the prior reference in history And Then the audit exports include the decision record identifier and link for traceability

Surge Sync

Coordinate surge windows across multiple squads and calendars in different time zones. Link teams to share a single expanded overlap, respect each group’s guardrails, and auto-stagger prep vs. live windows. Reduces cross‑functional chaos during incidents, launches, and EOQ crunch.

Requirements

Unified Surge Overlap Calculation
"As a remote program lead, I want Timeglue to calculate a single surge overlap across all squads so that we can coordinate work without forcing after-hours participation."
Description

Compute a single expanded overlap window across multiple squads and calendars in different time zones, honoring each team’s working hours, holidays, focus blocks, and existing commitments. The engine merges availability and proposes one or more contiguous surge windows that maximize cross-regional participation while minimizing after-hours impact. Supports constraints like minimum/maximum duration, earliest start/latest end by region, required role presence, and buffer times. Outputs candidate windows, confidence scoring, and constraint rationale. Integrates with Timeglue’s draggable timeline so admins can paint acceptable ranges and the system refines the final overlap. Provides an internal service and API to recalculate automatically on membership or calendar changes.

Acceptance Criteria
Core Unified Surge Overlap Calculation
Given multiple squads with defined working hours, holidays, focus blocks, existing commitments, and time zones And global constraints including min_duration and max_duration When the overlap engine is invoked with these inputs Then it returns one or more contiguous candidate windows in UTC and per-squad local time And each candidate overlaps availability across all included squads within their working hours And excludes any period that intersects focus blocks, holidays, or existing commitments by at least 1 minute And each candidate duration is within [min_duration, max_duration] And after-hours impact does not exceed any configured after-hours cap per squad (if set) And if no candidate is feasible, the response contains an empty set with result_code = "NO_FEASIBLE_WINDOW" and a non-empty list of blocking constraints
Required Roles And Buffers Enforcement
Given constraints specify one or more required roles that must be present concurrently And buffer_before and buffer_after values are configured (e.g., 10 minutes) When computing candidate surge windows Then each candidate includes continuous availability for all required roles for the entire window duration And buffer_before and buffer_after are free of conflicts for all participants involved in the candidate And any candidate that violates required role presence or buffers is excluded from results
Per-Region Earliest Start / Latest End Bounds
Given per-region earliest_start and latest_end constraints in local time When generating candidate windows across regions Then for every candidate and each represented region, local_start >= earliest_start[region] and local_end <= latest_end[region] And any candidate violating a region’s bound is not returned
Confidence Scoring And Constraint Rationale Output
Given a scoring model that favors higher participation and penalizes after-hours and soft violations When the engine returns candidate windows Then each candidate includes a confidence score in the range [0.00, 1.00] with two-decimal precision And candidates are sorted by descending score; ties break by earliest start, then shortest duration And each candidate includes a rationale listing satisfied constraints, applied penalties, and any near-miss constraints And scoring is deterministic for identical inputs
Draggable Timeline Paint Refinement
Given an admin paints one or more acceptable ranges on the timeline and requests refinement When the engine recomputes with the painted ranges Then only candidates fully contained within painted ranges are returned And if a candidate can be trimmed to fit within a painted range while meeting all constraints, it is snapped to the painted boundaries And candidates outside painted ranges are suppressed And the API returns the refined candidates within 2 seconds of the refine action
Automatic Recalculation On Membership Or Calendar Change
Given a squad membership change or calendar event (create/update/delete) is detected for any included participant When the internal service or public API receives the change Then a recalculation job is queued within 2 seconds and completed within 15 seconds for up to 10 squads and 200 participants And duplicate changes within a 5-second window are debounced into a single recalculation And results are idempotent for equivalent inputs (same candidate set and ordering) And an audit log entry is recorded including trigger type, inputs checksum, and outcome
Prep And Live Window Auto-Staggering
Given surge coordination requires a live window and a prep window with prep_offset (e.g., 30 minutes) before live And squads have distinct guardrails that must be respected When generating surge windows Then for each live candidate, a prep window is produced that ends exactly at live start and starts prep_offset earlier And both prep and live windows comply with all constraints (per-region bounds, buffers, role presence, and availability) And if a feasible prep window cannot be scheduled without violating constraints, the associated live candidate is deprioritized or excluded
Team Guardrails Compliance Engine
"As a people manager, I want guardrails to be enforced during surge scheduling so that my team isn’t scheduled outside protected hours."
Description

Enforce each team’s guardrails—work hours, no-meeting zones, focus blocks, regional holidays, and labor-law constraints—throughout surge planning and scheduling. Validate proposed surge windows, flag violations with clear explanations, and suggest compliant adjustments such as shifting start times, shortening duration, or splitting blocks across days. Support per-team overrides with justification, approver workflow, and auditable logs. Provide configurable tolerances and blackout dates. Integrate with overlap calculation, share links, and calendar publishing to ensure after-hours invites are automatically prevented.

Acceptance Criteria
Validate Proposed Surge Window Against Team Guardrails
Given a surge plan with multiple teams each having configured work hours, no‑meeting zones, focus blocks, regional holidays, labor‑law constraints, blackout dates, and tolerances When the user initiates validation for a proposed surge window Then the system evaluates the window against every guardrail per team across relevant time zones And for each team returns a verdict of Compliant, Compliant with Tolerance, or Violation(s) And for each violation returns: constraint type, affected time segment(s) in local team time, overshoot amount in minutes, and a reference to the guardrail configuration (ID/label) And the validation result is available to the UI and API in a single response without partial team omissions
Suggest Compliant Adjustments for Invalid Surge Window
Given a proposed surge window that fails validation for at least one team When the user requests suggestions Then the system returns at least three distinct compliant alternatives, if such exist, including at least one: shifted earlier, one shifted later, and one shortened or split across days And each suggestion includes: start/end times per team’s local time, total overlap minutes per team, a change description (shift/shorten/split), and a compliance status of Compliant And no suggestion intersects any team’s no‑meeting zone, focus block, holiday, or blackout date, nor exceeds configured labor‑law constraints And if fewer than three compliant alternatives exist, the system returns all available compliant alternatives and indicates the count
Per‑Team Override With Justification and Approval Workflow
Given one or more validation violations for a specific team When an authorized user initiates an override for that team Then the system requires a free‑text justification of at least 15 characters and a selected reason code from a configurable list And routes the override to the team’s designated approver(s) and prevents publishing until a decision is recorded And upon approval the system marks the exception scope (team, window, constraints waived) and effective time bounds, and revalidates remaining guardrails And upon denial the scheduling remains blocked for the violating segments And an auditable log entry is created including: requester, approver decision, timestamps, affected guardrails, justification, and before/after window details
Configurable Tolerances for After‑Hours Extensions
Given a team tolerance configured to allow up to N minutes beyond work hours per day during surges When validating a proposed surge window Then any overage per day per team ≤ N minutes is classified as Compliant with Tolerance and recorded with the exact overage minutes And any overage > N minutes is a violation And tolerance usage is surfaced in the UI/API and included in the audit log for the affected window
Enforce Blackout Dates and Regional Holidays
Given a team with configured blackout dates and a linked regional holiday calendar When computing overlap, validating a surge window, or generating suggestions Then no validated or suggested time segment may intersect a blackout date or holiday for that team And attempts to schedule over a blackout or holiday are blocked with an explanation referencing the specific date and source calendar
Guardrail‑Aware Overlap and Share Links
Given linked squads in Surge Sync and a generated share link for proposing surge times When a recipient opens the link and views/selects available times Then only time slots that are compliant for all teams (or marked Compliant with Tolerance where enabled) are displayed and selectable And attempts to submit a time outside any team’s guardrails are rejected with a clear error and no hold/event is created And the displayed times reflect each viewer’s local time while preserving per‑team compliance in their respective local times
Calendar Publishing Prevents After‑Hours Invites
Given a validated (or approved override) surge plan ready to publish to connected calendars When the user publishes events Then the system revalidates against the latest guardrails immediately prior to creation And if noncompliance is detected, the publish is blocked with explanations and suggestions, unless covered by an active approved override And no calendar event is created that would place any team member outside permitted hours or on blackout/holiday dates And successfully created events reflect correct time zones, durations, and participant lists per team
Auto‑Stagger Prep vs Live Windows
"As a launch coordinator, I want prep windows auto-staggered ahead of live windows so that teams can hand off work smoothly across time zones."
Description

Automatically schedule pre-surge preparation windows and live surge windows with intelligent staggering across time zones. Define dependencies so prep tasks occur during convenient hours for creators and reviewers, while live windows maximize cross-functional overlap. Support lead/lag offsets, rolling handoffs between regions, and rotation fairness to distribute inconvenient times. Allocate windows by role (prep, review, comms, ops, exec) and enforce guardrail compliance. Visualize staggered blocks on the timeline and update them as teams or calendars change.

Acceptance Criteria
Lead and Lag Offsets for Prep and Review
Given a live window with start time T and duration Dl And configured durations Dp for prep and Dr for review And configured lead offsets P for prep and R for review And creator and reviewer guardrails including work hours, holidays, and focus blocks When the schedule is generated Then a prep window of duration Dp ends at T minus P within creator guardrails And a review window of duration Dr ends at T minus R within reviewer guardrails And no prep or review window overlaps blocked time or violates guardrails And any guardrail-driven adjustment shifts the target offset by no more than 30 minutes while preserving order And the schedule records actual offset deltas in minutes for audit
Rolling Regional Handoffs Across APAC EMEA AMER
Given regions APAC, EMEA, and AMER with time zones and guardrails And a surge period with live window at T And a handoff overlap target of H minutes And a configured prep duration Dp per region When generating the prep sequence Then Dp-length prep blocks are scheduled in APAC then EMEA then AMER in chronological order And each prep block overlaps the next by at least H minutes within both regions' guardrails And no prep block starts or ends outside its region guardrails And if a region is unavailable due to holiday or blackout, its block is reassigned to the next region while maintaining overlap ≥ H And the final handoff completes at least 30 minutes before the earliest review window start
Rotation Fairness for Inconvenient Times
Given a cohort of eligible participants per role and a rolling 90-day period And inconvenient time defined by configured off-hours bands per participant When windows must be scheduled during inconvenient times due to constraints Then assignment rotates so no participant’s cumulative inconvenient minutes exceeds the cohort mean by more than 15 percent over 90 days And no participant is assigned two consecutive after-hours live windows if an eligible alternative exists And fairness metrics (inconvenient minutes per participant and rank) are computed and stored with the schedule
Role-Based Allocation with Guardrail Compliance
Given role mappings for prep, review, comms, ops, and exec with assigned members and calendars And guardrails per member including work hours, holidays, and focus blocks When generating staggered windows Then only members assigned to each role are invited to their respective windows And 100 percent of scheduled windows fall within all invitees’ guardrails And any window with less than 80 percent invitee availability is flagged and alternative slots within 48 hours are proposed And conflicts from mandatory events trigger automatic reflow within the same day when possible, else next available day within guardrails
Live Window Overlap Optimization
Given a set of mandatory roles and a live-window search horizon with granularity G And guardrails and calendars for all mandatory participants When optimizing the live window Then the selected live window maximizes overlapping availability minutes across mandatory roles And the chosen overlap score is within 2 percent of the theoretical maximum among candidates evaluated And ties are broken by earliest time then fairness rotation policy And the chosen window does not violate any guardrail or holiday for mandatory roles
Timeline Visualization and Auto-Update on Changes
Given the timeline view displaying the current staggered schedule When team membership, calendar events, or guardrails change Then the schedule recomputes within 60 seconds of detection And the timeline updates without page reload to show new prep, review, and live blocks And each block shows role label, region, participant count, and local times for all regions And a change summary lists added, removed, and shifted blocks with minute deltas And user manual adjustments are preserved if still compliant, else highlighted with suggested compliant alternatives
Dependency Enforcement from Prep to Review to Live
Given defined dependencies Prep before Review before Live When generating or updating the schedule Then no Review window starts before all required Prep windows end And no Live window starts before all Review windows end And any change that breaks dependencies triggers automatic re-sequencing or a blocking alert And if dependency-safe re-sequencing cannot maintain guardrail compliance, the schedule is marked Invalid and requires explicit override
Linked Calendar Sync (Google/Microsoft)
"As an incident commander, I want surge windows to sync to everyone’s calendars so that participation is clear and conflicts are minimized."
Description

Provide bi-directional synchronization with Google Calendar and Microsoft 365 for surge events and blocks. Pull availability signals (free/busy, out-of-office, focus time) for linked participants and push confirmed surge windows as events localized to each attendee’s time zone. Handle updates, cancellations, and attendee changes in near real time with conflict detection, retries, and idempotency. Support service accounts and delegated access with granular scopes, and implement rate limiting and backoff handling. Offer a dry-run preview before publishing and a per-event audit trail showing who and what changed.

Acceptance Criteria
Authenticate with Google/Microsoft Using Granular Scopes
Given an org admin initiates linking to Google or Microsoft 365 calendars When OAuth consent or service account credentials are provided Then only granular scopes are requested: read free/busy, read OOO/focus signals, and create/update/delete events on managed calendars And tokens are stored encrypted with KMS/HSM and refreshed automatically before expiry And delegated access is limited to specified users/mailboxes and can be revoked by the admin And a connectivity check (free/busy probe) succeeds with HTTP 200 within 5 seconds And scope revocation or token invalidation is detected within 5 minutes and the connection is marked Degraded with an actionable alert
Pull Availability Signals Across Linked Participants
Given participants across multiple time zones have linked Google/Microsoft calendars When a provider webhook fires or the periodic sync runs Then free/busy, out-of-office, and focus-time signals for the next 30 days are ingested and cached And the availability cache updates within 60 seconds of a provider-side change And OOO and focus-time blocks are treated as unavailable and override working hours And per-user guardrails (work hours, holidays) are applied to the computed availability And partial permissions or missing calendars are surfaced per user with a clear status (OK/Partial/Error)
Push Confirmed Surge Windows as Localized Events
Given a surge window is confirmed for multiple squads with linked calendars When the user publishes the surge to external calendars Then one event per attendee is created with start/end localized to the attendee’s time zone (UTC stored internally) And event title includes a standard prefix [Surge] and description contains Surge ID, shared overlap, and link back to Timeglue And the organizer is set to the configured service/delegated account per squad And attendees receive invitations according to provider defaults, or events are created without invites when configured And events appear on provider calendars within 60 seconds of publish And events are not scheduled outside attendee guardrails; any skipped attendees are listed in a publish report with reasons
Near Real-Time Update, Cancellation, and Attendee Change Propagation
Given a surge event has been published to Google/Microsoft When start/end, location, description, or attendees change in Timeglue Then corresponding updates propagate to external calendars within 60 seconds And cancellations send provider-native cancellations and remove external events And added attendees receive creates and removed attendees receive deletes And conflicts (pre-existing external modifications) are detected via etag/modifiedSince checks and resolved by re-fetch + retry And retries use idempotency keys (SurgeID+AttendeeID+StartUTC) with exponential backoff (2s, 4s, 8s) up to 3 attempts And no duplicate or orphaned events remain after retries complete
Rate Limiting and Error Handling for Providers
Given provider responses include rate limits (HTTP 429) or transient errors (5xx/timeouts) When syncing reads or writes Then operations are queued and executed under per-tenant and global concurrency caps And exponential backoff with jitter is applied up to 2 minutes before giving up And permanent errors (401/403/404) are classified and surfaced with actionable remediation steps And user notifications are batched to at most one notification per 15-minute window per surge And telemetry records operation, provider, error class, attempts, and latency for each failure
Dry-Run Preview Before Publishing
Given a surge plan with defined windows and linked participants When the user requests a dry-run Then a deterministic preview lists per-attendee create/update/delete operations with local times and conflict flags And no write calls are made to provider APIs during preview And the preview estimates API call counts and quota impact And the preview can be exported as JSON or ICS diff And publishing from the same revision executes exactly the operations shown in the preview
Per-Event Audit Trail and Traceability
Given any surge-related calendar operation occurs When a create/update/delete is attempted or completed Then an immutable audit entry is recorded with actor, action, before/after, UTC timestamp, provider, calendarId, eventId, and correlation/trace IDs And provider request/response IDs are captured when available And audit records are viewable in the UI within 10 seconds of the operation And audit exports can be filtered by date range, squad, and Surge ID And tamper-evident hashing is applied and verified on read of audit entries
Surge Templates & Scheduling Modes
"As a program manager, I want ready-made surge templates so that we can schedule quickly and consistently during high-pressure periods."
Description

Offer reusable templates for common scenarios—incident response, launch day, and EOQ crunch—with default durations, required roles, escalation policies, and notification cadences. Allow users to select a mode that auto-applies guardrails, overlap rules, and staggering patterns suited to the scenario. Support validity windows, recurrence, expiry, and workspace-level governance. Include versioning, cloning, and per-project edits with an audit trail of deviations from the base template.

Acceptance Criteria
Apply Incident Response Template in Incident Mode
Given a workspace has an "Incident Response" template with default duration 90 minutes, required roles [Incident Commander, Comms Lead], an escalation policy [L1 → L2 → On‑Call Manager], and notification cadence at T‑60, T‑15, T+0, T+45 minutes And a Surge Sync session links Squad A (PST) and Squad B (CET) When a user applies the "Incident Response" template using Incident Mode to create a session Then the session duration is set to 90 minutes And the required roles are enforced; saving is blocked with a validation error listing missing roles if any required role is unassigned And the escalation policy is attached and visible in the runbook panel And notifications are scheduled exactly at T‑60, T‑15, T+0, and T+45 relative to session start And the computed overlap window includes only times within both squads’ work hours, excludes holidays, and excludes hard focus blocks
Auto-Apply Guardrails and Overlap in Launch Day Mode
Given Launch Day Mode is configured to expand overlap up to +2 hours beyond base overlap without crossing any squad’s declared work hours, holidays, or hard focus blocks And three squads in different time zones are linked to a single Surge Sync When a user selects Launch Day Mode and generates a schedule Then the system computes a single overlap window that is ≥ the base overlap and ≤ base overlap + 2 hours And no portion of the window falls outside any squad’s work hours or within holidays/hard focus blocks And the shareable link proposes only times within the computed window And dragging the timeline outside guardrails snaps back and displays a tooltip explaining the violated guardrail
Auto-Stagger Prep and Live Windows Across Squads
Given a template defines staggering: Prep = 30 minutes before live per squad (respecting hard focus blocks), Live = 60 minutes aligned across all squads, Postmortem = 30 minutes after live for core team And roles are assigned: Prep = Eng Leads, Live = All squads, Postmortem = Core team When the template is applied to a Surge Sync with three squads across time zones Then the system schedules prep windows per squad starting 30 minutes before that squad’s local live window without overlapping hard focus blocks And a single live window is aligned across squads within their computed overlap And no individual is double‑booked across prep and live phases And calendar holds are created for Prep/Live/Postmortem with standardized titles and tags indicating phase and template version
Validity Window, Recurrence, and Expiry Enforcement
Given a template has a validity window from 2025‑09‑01 to 2025‑09‑30, a weekly recurrence on Tuesdays at 09:00 UTC, and an expiry of 2025‑10‑01 00:00 UTC When a user attempts to schedule a session outside 2025‑09‑01 to 2025‑09‑30 Then the system blocks scheduling and displays "Outside template validity window" When scheduling within the window Then recurring sessions are created on each Tuesday at 09:00 UTC and reflected in participants’ local times And after 2025‑10‑01 00:00 UTC, the template is marked Expired and cannot be applied to new sessions And existing scheduled sessions remain unaffected but display a "Based on expired template" badge And template owners receive a notification 7 days before expiry per workspace settings
Workspace Governance and Approvals for Template Use
Given workspace governance rules: only Admins may edit escalation policies; Project Maintainers may edit notification cadence; expanding overlap > +1 hour over base requires Workspace Owner approval When a Project Maintainer attempts to modify the escalation policy in a template or scheduled session Then the UI blocks the change and displays required permission scope When the same user edits only the notification cadence Then the change is allowed and logged to the governance audit log When any user attempts to expand overlap > +1 hour over base Then an approval request is created and sent to Workspace Owners, the change is marked Pending, and it is only applied upon approval; rejection reverts to the prior value with reason recorded
Template Versioning, Cloning, and Rollback
Given template "Launch Day" version v1.0 exists When a user edits and publishes updates Then version v1.1 is created, v1.0 remains immutable, a version ID is assigned, and a change summary is required and stored When a user clones v1.1 to a new template Then the clone receives a new Template ID, references its source (Template ID and version), and excludes the source’s audit events while retaining defaults When a project using v1.1 selects "Rollback to v1.0" Then future sessions use v1.0 defaults; past scheduled sessions remain unchanged; a rollback event is logged with user, timestamp, source version, and target version
Per-Project Edits with Audit Trail of Deviations
Given Project X adopts "EOQ Crunch" template v2.0 When a Project Admin overrides default duration from 120 to 90 minutes and removes the "Comms Lead" role Then the system requires an override reason and records an audit entry including user, timestamp, fields changed, before/after values, reason, and project ID And the effective project template displays a diff view vs base v2.0 highlighting deviations And removal of required roles triggers an approval workflow per governance rules; until approved, the base required roles remain enforced And the audit log is exportable as CSV and filterable by project, user, date range, field, and action type
Smart Share Links & Access Controls
"As a squad lead, I want to share a single link where stakeholders can review and approve a surge plan so that we avoid back-and-forth scheduling."
Description

Generate smart share links that expose proposed surge windows to stakeholders with role-appropriate permissions. Viewers see localized times, can propose alternatives within allowed ranges, and RSVP. Support role-based access, link expiration, domain restrictions, and SSO enforcement. Log all interactions and allow approvers to finalize windows directly from the link, triggering calendar publishing and notifications. Adapt available options to guardrails, hiding invalid times and preventing after-hours selections.

Acceptance Criteria
Smart Share Link Generation with Role Scopes
Given a Surge Sync draft with defined guardrails and team roles When the owner generates a smart share link and selects role permissions (Viewer, Contributor, Approver) and policies (expiresAt, domain allowlist, SSO required, max opens) Then the system creates a unique, non-guessable token (>=128-bit entropy) and persists link metadata (creatorId, surgeId, createdAt, expiresAt, scopes, policies, status=active) And the API returns the URL and metadata And accessing an invalid or expired token returns a neutral 404-style response without disclosing the surge’s existence And redacted content is enforced by scope (e.g., non-approvers cannot see finalize controls or hidden teams)
Localized View and Guardrail-Bounded Proposal
Given an authenticated Viewer or Contributor opens the link When the page loads Then all times render in the user’s detected IANA time zone with an option to change time zone, and switching zones re-renders immediately without altering stored UTC values And the timeline only displays selectable ranges that satisfy all teams’ guardrails (work hours, holidays, focus blocks) And invalid times are visually disabled and non-interactive When the user drags to propose an alternative within the allowed window Then the selection snaps to the configured increment (e.g., 15 minutes) and validates overlap across all teams before enabling Submit And proposals outside guardrails are blocked with an inline reason When the user submits a proposal Then the server records it in UTC with proposer identity and timestamp, and sends a notification to surge owners And rate limits apply (e.g., max 5 proposals per user per link per hour), returning a clear error if exceeded
RSVP Submission and Updates
Given a Viewer has access to a proposed or finalized window via the link When the user selects Attending, Maybe, or Decline Then the response is recorded with user identity (or captured email if anonymous is enabled), timestamp, and windowId And capacity constraints (if configured) are enforced with a clear error when full And RSVP counts update in near real-time for all open sessions (<=3s) And anonymous RSVPs are rejected unless the link policy explicitly allows them and passes domain checks
Approver Finalization, Calendar Publish, and Notifications
Given an Approver opens the link with at least one valid proposal available When the Approver selects a proposal and clicks Finalize Then a confirmation modal shows the affected teams and calendars And on confirm, the system writes the final window version, locks further proposals, and sets link status=finalized And events are created/updated on connected calendars (Google/Microsoft) for all teams with correct local times, titles, and descriptions, respecting configured prep vs. live staggering And success (or actionable failure details) is returned within 10 seconds And notifications are sent to stakeholders (email/Slack) including localized times And any calendar API failure results in a clear error, partial changes are rolled back or marked for retry with audit entries
Access Controls: Expiration, Domain Restriction, and SSO Enforcement
Given a link with an expiration timestamp When the current time is past expiresAt Then all accesses are blocked with an expired message and no surge details are revealed, and link status=expired Given a link with a domain allowlist When a user authenticates with an email outside the allowed domains Then access is denied with a policy message and the attempt is logged Given SSO enforcement is enabled for the link When a user without a valid SSO session opens the link Then they are redirected to the SSO provider and only granted access upon successful assertion (email, name, groups) mapped to roles When the owner revokes the link Then subsequent requests are blocked immediately and active sessions lose privileged actions on next interaction, with a revocation audit entry
Comprehensive Audit Logging of Link Activity
Given the smart share link is active When any relevant action occurs (open, access denied, proposal create/update/cancel, RSVP change, finalize, publish success/failure, settings change, revoke) Then an immutable audit record is stored with UTC timestamp (ISO8601), actorId (or anonymous), surgeId, linkId, action, outcome, errorCode (if any), IP (hashed), and userAgent And duplicate logs are prevented via idempotent action keys And authorized users can query logs by time range, action, actor, or outcome via UI/API And logs can be exported to CSV with sensitive fields redacted per policy And access to logs is restricted by RBAC
Guardrails Enforcement: Hidden Invalid Times and After-Hours Prevention
Given guardrails are configured for each team (work hours, holidays, focus blocks) When any user interacts with the timeline via the link Then times violating any team’s guardrails are hidden or disabled and cannot be selected or submitted And any backend attempt to create proposals outside guardrails is rejected with a specific error code and message And control visibility matches role: Viewers cannot change guardrails; Contributors can propose only; Approvers can finalize And the interface defaults to showing only the expanded overlap window and hides non-overlap periods to reduce error
Localized Notifications & Escalations
"As a distributed team member, I want localized reminders and clear escalations so that I don’t miss surge activities outside my immediate time zone."
Description

Send Slack, email, and in-app notifications with localized times for proposed, confirmed, and changed surge windows. Provide opt-in reminders aligned to each participant’s work hours, with escalation paths if approvals or RSVPs stall. Respect quiet hours by default while allowing incident-mode overrides with explicit confirmation. Offer per-role reminder cadences and a status board that reflects acknowledgements and readiness across teams.

Acceptance Criteria
Localized Times in Multi-Channel Notifications
Given a surge window is proposed, confirmed, or changed and a recipient has a time zone set When a Slack, email, or in-app notification is generated Then the start and end times display in the recipient’s local time with TZ abbreviation and UTC offset And the notification includes a secondary UTC reference And daylight saving and cross-date-boundary cases are rendered correctly for the recipient’s locale And time values match the canonical UTC schedule within ±60 seconds across all channels
Work-Hour Aligned Opt-In Reminders
Given participant work hours, holidays, and focus blocks are configured and the user has opted in to reminders When reminder schedules are created for a surge window Then reminder send times fall within the participant’s defined work hours and avoid holidays/focus blocks And any reminder that would land in quiet hours is deferred to the next allowed window And deferrals are logged with original and new send timestamps And users can opt out per channel without affecting other channels
Quiet Hours with Incident-Mode Overrides
Given quiet hours are enabled by default When a notification is scheduled during a participant’s quiet hours Then the notification is suppressed and queued for the next work window And if incident mode is enabled for the surge and an authorized user explicitly confirms an override Then the notification is delivered immediately with an "Override Quiet Hours" indicator and an audit record capturing who, when, and why And override confirmation requires an explicit action (typed confirmation or checkbox) and is recorded per surge
Escalation for Stalled Approvals and RSVPs
Given an approval or RSVP request remains unacknowledged beyond the configured threshold When the threshold elapses Then the system escalates to the next contact per the escalation matrix via Slack and email And the original assignee is re-notified and the escalation is capped at the configured maximum attempts And escalations cease immediately upon any acknowledgement And the status board updates to show Escalated with timestamps and the current escalation level
Per-Role Reminder Cadences
Given role-based reminder cadences are configured (e.g., IC, Tech Lead, Comms) When a surge window is confirmed Then reminders are scheduled relative to prep and live windows per each role’s cadence And updating the cadence or surge window automatically reflows future reminders without duplicating past sends And reminders are deduplicated across channels within a 5-minute window per user And channel preferences (Slack/email/in-app) are honored for each role and user
Status Board Acknowledgements and Readiness
Given a surge is active and notifications are being sent When deliveries, views, acknowledgements, and readiness confirmations occur Then the status board reflects per participant: Delivered, Seen, Acknowledged, and Ready states with localized timestamps And team/role filters and search return results in under 1 second for up to 1,000 participants And board updates appear within 3 seconds of a state change And exporting the board yields a CSV including user, team, role, state transitions, timestamps, and channel used
Change Notifications and Rescheduling
Given a previously confirmed surge window is modified When the change is published Then all affected participants receive change notifications showing old vs. new times in their local time and UTC And all previously scheduled reminders for the old window are canceled and regenerated for the new schedule And prior approvals/RSVPs affected by the change are invalidated and re-requested with clear callouts And the status board indicates Change Pending until required re-acknowledgements are received

Cooldown Shield

Automatically schedules a post‑surge recovery period for impacted people and teams. Temporarily tightens booking windows, prioritizes focus blocks, and proposes Time Debt payback slots—helping morale and compliance rebound while nudging calendars back to normal.

Requirements

Adaptive Surge Detection & Triggers
"As a remote team lead, I want cooldowns to trigger automatically when workload spikes so that my team recovers without me micromanaging calendars."
Description

Continuously monitor calendar signals (meeting volume spikes, after-hours invites, shortened buffers, incident tags) to automatically detect workload surges and initiate a cooldown. Supports org/team/individual scopes, manual trigger via UI/API, and inbound webhooks from incident systems. Thresholds are configurable by team and time zone, respect Timeglue’s work-hour/holiday rules, and consider painted acceptable-hour timelines. On trigger, create a cooldown record with start/end, impacted cohort, policy version, and rationale. Includes debounce and false-positive suppression, sensitivity presets, and an event log for observability. Integrates with Timeglue’s scheduling engine so downstream enforcement and proposals activate immediately.

Acceptance Criteria
Surge detection on meeting volume spike within work hours
Given team "APAC Sales" is configured with local time zone Asia/Singapore, work hours 09:00–18:00, holidays enabled, and an acceptable-hour painted timeline And thresholds: meeting_volume_spike = ≥25% over 7-day moving baseline within a 4-hour window AND min_delta_meetings = 5 When within a 4-hour period on a business day inside local work hours, the counted meetings (excluding holidays and outside the painted acceptable-hour timeline) exceed the baseline by ≥25% and by ≥5 meetings Then a cooldown is created within 60 seconds of detection And the cooldown record contains: start_time (UTC ISO 8601), projected_end_time per active policy, impacted_cohort = team:APAC Sales, policy_version id, and a rationale that includes metric values and baseline window And the scheduling engine activates tightened booking windows and focus-block prioritization within 60 seconds of cooldown creation
After-hours invite surge triggers immediate cooldown enforcement
Given team "NA Eng" has thresholds: after_hours_invites >= 3 within 2 hours, where after-hours = outside the team's painted acceptable-hour timeline and work-hour rules by local time zone When 3 or more new invites are created in a 2-hour window outside acceptable hours Then a cooldown is created and marked active within 60 seconds And booking attempts via Timeglue smart links outside tightened windows are blocked with HTTP 409 and error_code = COOL_DOWN_ACTIVE And the scheduling engine proposes at least 2 Time Debt payback slots per impacted person within the next 5 business days, visible via UI/API within 2 minutes And the cooldown record includes counts of after-hours invites contributing to the trigger
Debounce and false-positive suppression during transient spikes
Given debounce_window = 30 minutes and persistence_required = 15 minutes When a surge condition is met but falls below threshold within 10 minutes Then no cooldown is created and an event log entry is recorded with outcome = suppressed and reason = transient_spike When a second surge occurs during an active cooldown and within the debounce_window Then no additional cooldown is created; the existing cooldown remains unchanged and an event is logged with outcome = suppressed and reason = debounce When a surge persists above threshold continuously for ≥15 minutes after the previous cooldown ended Then a new cooldown may be created (subject to debounce rules) and is recorded with a unique cooldown_id
Manual UI/API trigger and incident webhook integration
Given a user with role Team Lead for team "Ops EMEA" clicks "Start Cooldown" in the UI When the action is confirmed Then a cooldown record is created for team scope with required fields (start_time, projected_end_time, impacted_cohort, policy_version, rationale = user_trigger) and enforcement activates within 60 seconds Given an authenticated API client calls POST /v1/cooldowns/triggers with body {scope:"team", target_id:"ops-emea", rationale:"api_trigger"} and valid HMAC signature When the request is processed Then HTTP 201 is returned with cooldown_id and enforcement activates within 60 seconds; the event log records actor_type = api Given an inbound webhook from the incident system with severity = SEV1 and mapped team = "Ops EMEA" When the webhook signature is valid and payload passes schema validation Then a cooldown is created with rationale including incident_id; if signature invalid, return HTTP 401 and do not create a cooldown
Scope-aware cooldown application (org/team/individual)
Given an individual-scope surge is detected for user U in team "Design LATAM" When the cooldown is created Then only U’s booking windows are tightened and focus blocks prioritized; other team members remain unaffected Given a team-scope surge is detected for team "Design LATAM" When the cooldown is created Then all active members of the team inherit the tightened windows, respecting each member’s local work hours/holidays and excluding members marked PTO Given an org-scope surge is triggered for org "Acme Co" When the cooldown is created Then all teams under the org receive the policy; maximum duration does not exceed org policy limit; local time zone rules are applied per member
Sensitivity presets and team-specific threshold overrides
Given presets are defined as: High = {meeting_volume_spike: 20%, window: 4h}, Medium = {25%, 4h}, Low = {35%, 6h} And team "Support ANZ" selects preset High and overrides meeting_volume_spike to 18% When a surge evaluation runs Then the effective threshold for the team is 18% in a 4h window And the UI/API shows the resolved thresholds including overrides And baseline calculations use the team’s local business days and work-hour windows for normalization When the team switches preset to Medium without overrides Then the effective threshold becomes 25% in a 4h window and is applied on the next evaluation cycle
Event log and cooldown rationale observability
Given any trigger attempt (automatic, manual, API, webhook) occurs When the system processes the attempt Then an event log entry is written with fields: event_id (UUIDv4), timestamp (UTC ISO 8601), actor_type, scope, org_id/team_id/user_id, rule_ids evaluated, metrics_snapshot, rationale (<=500 chars), cooldown_id (nullable), outcome (triggered|suppressed|error), response_time_ms And event logs are queryable via UI/API with filters for time range, scope, actor_type, outcome, and team/org/user And event payloads contain no PII (e.g., meeting titles) and pass redaction rules And logs are retained for ≥90 days and downloadable as CSV with the same filters
Configurable Cooldown Policy Engine
"As an operations manager, I want standardized cooldown rules per team so that responses are consistent and compliant across regions."
Description

Provide a policy framework that defines how cooldowns behave: duration, booking-window tightening (e.g., shrink acceptable hours on the draggable timeline), daily meeting caps, maximum meeting lengths, buffer expansion, focus-block quotas, and gradual normalization curves. Policies are time-zone aware, honor holidays, and support role-based exceptions (e.g., on-call, customer success). Include policy templates, versioning with effective dates, dry-run mode, and conflict resolution against existing Timeglue constraints. Expose policies via admin UI and API, store them per team/org, and attach the active policy to each cooldown instance for auditability.

Acceptance Criteria
Create and Version a Team-Scoped Cooldown Policy via Admin UI and API
Given I am an org admin of team T with Admin UI access When I create a cooldown policy with required fields (name, duration, booking-window tightening, daily caps, max meeting length, buffer minutes, focus-block quota, normalization curve, time-zone/holiday rules, role exceptions, effective date) Then the policy is saved as version 1 scoped to team T and returns an id and version Given a valid API token for team T When I POST the same policy to /teams/{T}/cooldown-policies Then I receive 201 Created with the policy id and version and the record matches the UI-created policy schema Given built-in policy templates are available When I instantiate template "Post-Outage Cooldown" for team T and adjust parameters Then a new policy version is created for team T linked to the template id and marked Draft until published Given an existing policy with version 1 and an effective date in the future When I publish version 2 with a later effective date Then version 1 remains effective until its effective date and version 2 becomes effective at its own effective date; past versions are immutable Given invalid input (missing required fields, negative durations, past effective dates without override) When I attempt to save via UI or API Then the system rejects with validation errors (HTTP 400 for API) and no policy version is created
Apply Booking-Window Tightening on Draggable Timeline Across Time Zones and Holidays
Given a policy with booking-window tightening of 30% and an active cooldown for team T When an organizer in PST and an invitee in CET open the scheduling timeline Then each sees acceptable hours reduced by 30% relative to their own working hours in their local time zones Given regional holidays are configured for both participants When a proposed slot falls on a holiday for either participant Then that slot is unavailable and cannot be booked or suggested Given the minimum acceptable window is 60 minutes When tightening would result in less than 60 minutes of acceptable time Then the system clamps to 60 minutes and displays a cooldown notice explaining the constraint When a smart booking link is opened during an active cooldown Then only tightened windows are presented and attempts outside them are blocked with a message referencing the active cooldown policy
Enforce Daily Meeting Caps, Max Lengths, Buffer Expansion, and Focus-Block Quotas
Given a policy with a daily meeting cap of 4 per participant and a max meeting length of 45 minutes When scheduling a 5th meeting for a participant on the same day or a meeting longer than 45 minutes Then the scheduler blocks the action and surfaces the specific violated rule Given a policy with buffer expansion of 15 minutes When placing a meeting adjacent to another Then the scheduler enforces at least 15 minutes before and after; if not possible, it blocks the booking and offers the nearest compliant alternatives Given a policy with a daily focus-block quota of 120 minutes per participant When the day lacks sufficient focus time Then the scheduler reserves or proposes focus blocks to reach 120 minutes before confirming new meetings; if the quota cannot be met, the booking is denied with an explanation
Honor Role-Based Exceptions During Cooldown
Given a policy that exempts On-Call roles from daily caps and applies a different max length for Customer Success When scheduling includes users with those roles Then the engine applies the exception rules per user accordingly Given a multi-attendee meeting with mixed roles When computing constraints Then the most restrictive applicable rule per constraint is used except where a user’s role is explicitly exempt from that constraint Given a user without any exception role When scheduling during cooldown Then standard cooldown policy rules apply to that user
Execute Gradual Normalization Curve to Return to Baseline
Given a policy with a linear normalization curve over 7 days from 50% tightening to 0% When day D of the cooldown is in progress Then booking-window tightening, daily caps, buffers, and focus-block quotas are calculated according to the curve for day D Given the cooldown end date is reached When scheduling after that time Then baseline (pre-cooldown) constraints are restored automatically for new bookings Given meetings scheduled earlier in the cooldown When constraints relax on subsequent days Then existing bookings remain unchanged; only future scheduling uses the updated constraints
Run Dry-Run Simulation and Report Conflicts Without Enforcement
Given dry-run mode is enabled for a draft policy over a specified date range When the simulation is executed Then the system produces a report summarizing projected impacts (counts of blocked meetings, tightened windows applied, focus quota shortfalls) and representative examples; no changes are made to live schedules Given existing Timeglue constraints (e.g., org blackout periods, user working hours) When conflicts are detected during simulation Then the engine resolves using the rule "most restrictive constraint wins" and records which constraint prevailed for each case in the report Given a draft policy with validation errors When running a dry-run Then the system returns validation errors and does not generate a report
Store and Retrieve Active Policy Snapshot on Cooldown Instance
Given a cooldown instance is created for team T When it is started Then the system attaches a snapshot of the resolved policy version (policy id, version, effective date, parameters, exception rules, curve settings) to the instance for audit When retrieving the cooldown instance via UI or GET /cooldowns/{id} Then the attached policy snapshot is present, readable, and immutable even if newer policy versions are published later Given an audit export is requested for the cooldown instance When exporting Then the export includes the attached policy snapshot and relevant timestamps (created, started, ended)
Booking Window Tightening Enforcement
"As a meeting organizer, I want scheduling links to show limited slots during cooldown so that I don’t accidentally book over recovery time."
Description

When a cooldown is active, enforce stricter availability across Timeglue smart links and calendar integrations: narrow bookable windows, extend buffers, cap daily meetings, and hard-block after-hours. Update scheduling pages in real time to reflect reduced slots while honoring previously painted acceptable hours and existing commitments. Provide progressive re-expansion as cooldown decays. Include override flows with justification, API errors with actionable messages, and safeguards to prevent double-booking. Works with Google/Microsoft calendars and respects focus blocks and holidays.

Acceptance Criteria
Real‑Time Tightening on Smart Links During Active Cooldown
Given a host with painted acceptable hours 09:00–17:00 local and an active cooldown policy {window: 10:00–15:00, bufferExtension: +15m, dailyCap: 4, afterHours: 18:00–09:00} And the host has existing meetings 11:30–12:00 and 14:00–14:30 on the selected day When a guest opens the host’s Timeglue smart link for that day Then only slots fully within 10:00–15:00 are shown And slots overlapping the meetings plus a 15-minute buffer on both sides are not shown And no slots outside 09:00–17:00 are shown And once 4 meetings are booked for that day, no additional slots are offered for that day on any smart link And attempts to select a now-hidden slot are blocked with a clear message explaining cooldown limits
Calendar Integration Enforcement Across Google/Microsoft with Focus Blocks and Holidays
Given a host connected to Google Calendar with a focus block 13:00–15:00 and a full-day holiday on 2025-09-15, and a cooldown policy {window: 10:00–15:00, bufferExtension: +15m} When availability is fetched via UI or API for 2025-09-15 Then no slots are shown for that date due to holiday When availability is fetched for 2025-09-16 Then slots between 10:00–15:00 are shown except 13:00–15:00 (focus block) And any booking created is inserted into Google Calendar within 30 seconds with 15-minute buffers And for Microsoft 365 connected calendars the same rules apply and the event appears within 30 seconds And no slot is offered that overlaps with focus blocks, holidays, or existing events including buffers
Progressive Re‑Expansion as Cooldown Decays
Given a cooldown configured to start at T0 with window 10:00–15:00, buffer +15m, dailyCap 4 and decay to normal window 09:00–17:00, buffer +5m, dailyCap 8 at T0+48h with linear interpolation When the clock reaches T0+24h Then availability reflects window 09:30–16:00, buffer +10m, dailyCap 6 across UI and API within 60 seconds And when the cooldown ends at T0+48h Then availability returns to 09:00–17:00, buffer +5m, dailyCap 8 within 60 seconds And re-expansion never exceeds the painted acceptable hours and working hours settings
Override Booking Within Cooldown With Mandatory Justification
Given a user with role Manager has override permission and a cooldown is active And the requested slot is within working hours but outside the tightened window or exceeds the daily cap When the Manager attempts to book via UI Then the system requires a justification (min 20 characters) and a reason code selection before enabling Confirm And upon confirmation, the booking is created if it does not violate after-hours hard block And an audit record is stored with: who, when, justification text, reason code, overridden rules, and affected attendees And a visible flag “Override” appears on the event in the UI and API payloads And non-manager users cannot access the override flow
API Error Responses With Actionable Messages
Given a client calls POST /bookings for a slot that violates cooldown (after-hours, outside tightened window, cap reached, or buffer conflict) When the request is processed Then the API responds 409 Conflict with a machine-readable code (e.g., cooldown.after_hours | cooldown.window | cooldown.cap_reached | cooldown.buffer_conflict) And the response includes a human-readable message, correlationId, affected rule, remainingDailyQuota, cooldownEndsAt, and up to 5 suggested next slots And error messages are localized based on Accept-Language And retrying with a suggested slot returns 201 Created when valid
Double‑Booking Safeguards Under Tight Windows
Given two guests attempt to book the same slot within 1 second of each other during an active cooldown When both submit Confirm Then only one booking is created and the other receives 409 Conflict (code: slot_taken) with refreshed suggested slots And the system uses an atomic hold on the slot for up to 90 seconds during checkout and releases it on cancel/timeout And no calendar shows overlapping events for the host
Real‑Time Scheduling Page Updates on Cooldown Toggle
Given a guest has a host’s scheduling page open and no slot selected And an admin toggles a cooldown on that affects the host’s availability When the change is saved Then within 10 seconds the page updates without reload to reflect reduced slots And any slots that became invalid are immediately disabled with a tooltip explaining the cooldown rule And if the guest tries to book an invalidated slot, the booking is blocked with an inline error and the UI refreshes to current availability
Focus Block Auto-Prioritization
"As an engineer, I want guaranteed focus time after a surge so that I can catch up and reduce burnout."
Description

Automatically create, extend, and protect focus blocks for impacted users during cooldowns. Use heuristics to select optimal times within work hours, align with the draggable acceptable-hours timeline, and avoid critical meetings. Optionally reschedule low-priority internal meetings and suggest alternatives. Mark focus blocks with elevated calendar priority to resist displacement, color-code them, and notify affected parties. Preferences (minimum block size, daily targets, morning/afternoon bias) are configurable per user/team and obey holidays and time zones.

Acceptance Criteria
Create Focus Blocks Within Work Hours, Acceptable Hours, Holidays, and Time Zones
- Given a user with work hours 09:00–17:00 local, acceptable-hours timeline 10:00–16:00, and an active cooldown, When the scheduler runs for the day, Then it creates focus blocks only within 10:00–16:00 and within 09:00–17:00 in the user's time zone. - Given a company holiday or user PTO on the day, When the scheduler runs, Then it does not create new focus blocks on that day. - Given a daylight saving time change on the date, When the scheduler runs, Then block start and end times reflect the correct local UTC offset for that date.
Honor Minimum Block Size, Daily Target, and Morning/Afternoon Bias
- Given preferences minBlockSize = X minutes, dailyTarget = Y minutes, bias = morning, When scheduling a day with sufficient availability, Then the system schedules the fewest number of blocks with length >= X that total >= Y and places them before 12:00 local when feasible. - Given existing focus blocks are present, When the dailyTarget is not met, Then the system first extends existing blocks (without exceeding acceptable-hours or work hours) before creating new blocks. - Given limited availability, When the dailyTarget cannot be fully met, Then the system schedules as much protected time as possible using blocks of length >= X and flags the shortfall in the user notification.
Avoid Critical Meetings and Reschedule Low-Priority Internal Meetings
- Given a potential conflict with a critical or external-facing meeting, When choosing focus block times, Then the system neither overlaps nor moves those meetings. - Given a conflict with a low-priority internal meeting and auto-reschedule is permitted by policy, When scheduling focus blocks, Then the system automatically reschedules the meeting into the next suitable window within two business days that fits attendees' acceptable hours. - Given auto-reschedule is not permitted, When a conflict is detected, Then the system proposes at least two alternative time suggestions to the meeting organizer without creating the conflicting focus block.
Elevated Priority, Color Coding, and Displacement Resistance
- Given focus blocks created during cooldown, When viewed in the calendar UI, Then they display with a distinct color and a label indicating their elevated priority (e.g., "Cooldown Focus"). - Given elevated calendar priority is enabled, When someone attempts to book via a Timeglue link overlapping a cooldown focus block, Then those times are excluded from bookable options. - Given a manual event is created overlapping a cooldown focus block, When the event is saved, Then the system requires an explicit override confirmation by the user/admin; otherwise, the invite is declined and the focus block remains.
Notifications to Affected Parties
- Given new, extended, or moved focus blocks are scheduled, When the operation completes, Then the user receives a notification summarizing block times, total protected time vs daily target, and a link to adjust preferences. - Given a low-priority internal meeting is rescheduled due to cooldown, When the reschedule is committed, Then all affected attendees receive updated invitations and a brief explanation of the cooldown context. - Given no scheduling changes are needed for a day, When the scheduler runs, Then no notification is sent for that day.
Team and User Preference Hierarchy and Inheritance
- Given team-level defaults and user-level overrides, When the scheduler runs, Then user-level overrides take precedence; otherwise, team defaults apply. - Given a user updates their preferences, When the next scheduling cycle runs, Then changes take effect the next business day. - Given team members operate in different time zones/local holidays, When scheduling cooldown focus blocks, Then each member's blocks are computed in their local time and skip their local holidays while honoring each member's acceptable-hours timeline.
Time Debt Payback Scheduling
"As a team member, I want compensatory time slots proposed automatically so that my after-hours effort is balanced without extra admin work."
Description

Detect after-hours or overflow work performed during a surge, compute time debt per user, and propose compensatory time slots within the next configurable window. Present suggestions in-app and via Slack/email, allow manager approval or adjustment, and auto-schedule upon approval. Ensure proposals avoid critical periods, respect personal constraints, and coordinate across time zones for fairness. Track remaining debt, partial paybacks, and sync state to calendars. Provide policy hooks for labor compliance and PTO alignment.

Acceptance Criteria
Time Debt Detection During Surge
Given a surge period is defined and users’ working hours, holidays, focus blocks, and PTO are configured And after-hours or overflow work is recorded as calendar events or tracked work items associated with the surge When time debt is computed Then the system calculates per-user debt in minutes using the configured rounding increment and daily cap rules And excludes time on holidays/PTO and within personal working hours And creates or updates a single idempotent debt record per user per surge with source references and totals And recomputation produces identical totals without double-counting
Payback Slot Generation Within Configurable Window
Given a user has outstanding time debt and a payback window of N business days after surge end is configured When generating payback proposals Then the system proposes slots totaling the configured percentage of outstanding debt rounded to the minimum block size And avoids conflicts with existing events, focus blocks, critical periods/blackouts, holidays, PTO, and outside personal working hours And limits proposals to a maximum of M slots per day and respects earliest/latest daily bounds And provides reason codes for skipped time ranges and includes local times and time zones in the proposal And does not propose slots beyond the configured window end
Multi-Channel Proposal Delivery and Acknowledgment
Given proposals are generated for a user When notifications are sent Then an in-app notification appears within 1 minute and Slack or email is delivered within 5 minutes And the message includes total debt, proposed slots with local times, and actionable links to Accept, Adjust, or Reject And if Slack delivery fails or the user is not connected, email is sent as a fallback And links are single-use, signed, and expire after 7 days
Manager Approval, Adjustment, and Auto-Scheduling
Given a manager is assigned to the user/team and proposal links are used When the manager approves all or selected proposals Then the system validates constraints again and schedules calendar holds for approved slots with status Busy and description indicating Time Debt Payback And sends confirmations to the user and manager and updates the debt ledger immediately And if a slot conflicts at scheduling time, an alternative is auto-suggested within the same window and requires re-approval unless auto-approve is enabled And when the manager adjusts slot duration/time within allowed bounds, the system revalidates and updates totals before scheduling And on rejection with reasons, the system records the reason and regenerates alternatives within 1 minute
Cross-Timezone Fairness Allocation
Given multiple impacted users across time zones with comparable constraints When payback slots are allocated for the same recovery window Then no slot is scheduled outside any user’s personal working hours And the distribution of local start-hour bins per user differs by no more than one block from the team median over the window, within a configurable fairness threshold And rotation ensures that if early or late edge slots are necessary, the assignment rotates across users in successive paybacks And a fairness report is generated showing per-user local-hour distribution and rotation order
Tracking, Partial Paybacks, and Calendar Sync
Given payback events are scheduled When events are created, modified, or canceled on connected calendars Then the system updates remaining debt based on actual scheduled minutes still on the calendar within 10 minutes And partial paybacks reduce debt accordingly and ledger entries show before/after balances with timestamps and source event IDs And if an event is canceled externally, the system reopens the equivalent minutes as debt and proposes replacements within the original window if time remains And state is available in-app and via API for per-user debt, scheduled paybacks, and remaining balances
Compliance and PTO Alignment Hooks
Given labor compliance policies and PTO alignment hooks are configured for the organization/region When generating or scheduling payback slots Then the system enforces policies such as minimum rest periods, maximum daily/weekly hours, consecutive days limits, and union rules where applicable And no proposal is created that would violate a policy; violations are logged with policy codes and shown to the manager And approved slots are adjusted to align with upcoming PTO where allowed by policy, or excluded otherwise And a policy hook callback is invoked with the proposal payload and returns approve/transform/reject outcomes that are applied
Cooldown Communications & Overrides
"As a project lead, I want clear notifications and a controlled override path so that stakeholders stay informed while necessary exceptions are captured."
Description

Deliver clear, multi-channel communications when cooldowns start, change, or end: in-app banners, calendar annotations, email, and Slack posts. Scheduling pages display a cooldown badge and summary of temporary rules. Provide a controlled override workflow with reason capture, approver routing, and time-bound exceptions. Localize content, ensure accessibility, and link to the relevant policy. Maintain an audit trail of messages, acknowledgments, and overrides for transparency.

Acceptance Criteria
Cooldown Start — Multi‑Channel Notification Delivery
Given a cooldown is activated for a defined team or org segment with email, Slack, and calendar integrations configured When the cooldown status changes to Active Then an in-app banner is displayed to impacted signed-in users within 10 seconds of page load or via live update And an email notification is sent to all impacted users within 2 minutes And a Slack message is posted to the configured channel(s) within 1 minute And calendar annotations are created on impacted users’ connected calendars for the cooldown date range within 5 minutes And the scheduling page displays a “Cooldown Active” badge and a rules summary And all messages include a working policy link and the cooldown start/end in each recipient’s local time And duplicate notifications for the same event are prevented per user per channel for 24 hours And the audit trail records the event and delivery status per channel
Cooldown Change or End — Update & Rollback
Given an Active cooldown exists When cooldown parameters change (scope, dates, rules) Then updated notifications are sent across channels (in-app within 10s, Slack within 1m, email within 2m) And prior calendar annotations are updated to reflect the new parameters within 5 minutes And the scheduling page badge and summary reflect the new rules within 10 seconds Given an Active cooldown exists When the cooldown ends Then the in-app banner is removed on next view or within 10 seconds And a closure notice is sent via Slack within 1 minute and email within 2 minutes And calendar annotations are removed or marked complete within 5 minutes And the scheduling page removes the badge within 10 seconds And all changes are captured in the audit trail
Scheduling Page Badge & Rules Summary
- Badge is visible on all public and internal scheduling pages affected by a cooldown and hidden otherwise - Badge shows status (Upcoming/Active/Ending Soon), an accessible icon (aria-label), and a tooltip/expandable panel - Summary lists temporary rules: tightened booking windows, focus block prioritization, Time Debt payback slot proposals, and override availability - Displays cooldown start/end in viewer’s local time with UTC shown on hover or secondary text - Includes a locale-specific policy link opening in a new tab, with UTM tracking parameters - Content is localized to viewer locale with fallback; text wraps without truncation on mobile and desktop - Badge and summary elements are keyboard reachable, screen-reader announced, and meet contrast requirements - Page renders the badge within 200ms of initial content load when cooldown is Active
Override Request — Eligibility & Reason Capture
Given a requester selects a timeslot blocked by cooldown rules on a scheduling page When they choose Request Override Then an override form is shown requiring a reason (min 10 characters) and a category selection And requester identity (name/email) and target team/person are prefilled or required And the form allows proposing one or more preferred times within a policy-defined exception window And submission is blocked until all required fields are valid And the confirmation screen shows a tracking ID and estimated response SLA And an audit record is created with requester, reason, proposed times, and UTC timestamp And the requester receives an email receipt within 2 minutes
Override Approval — Routing, SLA, and Enforcement
Given an override request is submitted When routing rules are evaluated Then the request is assigned to the correct approver based on team/org policy, with fallback to a default queue if no match And if no action is taken within the SLA (e.g., 8 business hours), the request auto-escalates to the next approver tier And approvers can Approve, Deny, or Request Changes, each requiring a decision note And an Approval creates a time-bound exception that only permits booking within the approved window and defined scope And a Denial blocks the requested slot and triggers suggested alternative times compliant with cooldown rules And all decisions notify the requester via email and Slack (if connected) and update the audit trail with actor, decision, timestamp, and reason And pending requests past the exception expiry auto-expire with notifications and audit entry
Localization & Timezone Formatting for Communications
- All cooldown communications (in-app, email, Slack, badge, calendar annotations) render in the recipient’s locale with ≥95% translation coverage; missing strings fall back to English - Dates/times display in the recipient’s local timezone with clear abbreviations and include ISO-8601 timestamps in machine-readable attributes - Policy links resolve to locale-specific documents when available, else fall back without error - RTL languages render correctly (layout mirroring, punctuation, numbers) - Pseudo-localization test at 120% string length shows no truncation/overlap on common breakpoints (320px, 768px, 1280px) - Language preference respects user profile and Accept-Language header and persists across sessions
Audit Trail — Messages, Acknowledgments, and Overrides
- For each cooldown event (start/change/end), create an immutable audit entry with event type, timestamp (UTC), initiator (user/system), scope (teams/users), and versioned payload snapshot - For each notification channel, record delivery status (queued/sent/delivered/failed), provider message ID, retry attempts, and final outcome - Record user acknowledgments of in-app banners with user ID and timestamp; provide a query of unacknowledged users - For each override, capture requester, approver(s), reason, decision, decision notes, exception window, and linked booking ID if approved - Audit entries are filterable by date range, team/user, and event type in the admin UI and exportable to CSV and JSON with checksum - Access to the audit log is permission-controlled and audited; PII is minimized and redacted on user deletion per policy - New audit entries become visible in the admin UI within 60 seconds of occurrence
Cooldown Analytics & Compliance Audit
"As an HR and compliance officer, I want an audit trail and metrics of cooldowns so that we can demonstrate policy adherence and measure recovery effectiveness."
Description

Offer dashboards and exports that quantify cooldown impact and compliance: number/duration of cooldowns, affected users, meeting volume before/after, focus time gained, time debt accrued/paid, override rates, and normalization timelines. Provide org/team filters, API access, and data retention controls. Link metrics to specific policy versions and cooldown records for traceability. Support audit-ready reports that demonstrate adherence to labor and after-hours policies across regions.

Acceptance Criteria
Cooldown Impact Dashboard Metrics
Given a date range and selected organization/team filters When the Cooldown Analytics dashboard loads Then it displays metrics: total cooldowns initiated, average and median cooldown duration (hh:mm), count of affected unique users, meeting volume before vs after (per baseline window), focus time gained (minutes), time debt accrued (minutes) and paid (minutes), override rate (%), and normalization timeline (days to baseline) Given the same filters When the user compares “before” and “after” meeting volume Then “before” is computed over the baseline window (default 14 days pre‑cooldown) and “after” over the cooldown period plus 14 days post‑cooldown, unless overridden by filter settings Given a seeded validation dataset When metrics are recalculated Then values match the expected control totals with zero variance (exact match after rounding to nearest minute where applicable)
Traceability to Policy Versions and Cooldown Records
Given any metric widget or table row When the user clicks View Details Then a drill‑down lists underlying cooldown records with cooldown_id, policy_version_id, timestamps, region, team_id, and affected user identifiers Given an export or API response When it is generated for the same filters Then it includes traceability fields: cooldown_id, policy_version_id, policy_effective_at, policy_name, region, team_id, user_id (or hashed_id), and source_event_ids when applicable Given a displayed aggregate value When the user opens its drill‑down Then the sum/count of underlying items equals the displayed aggregate
Org/Team/Region Filters and Segmentation
Given filters for organization, team (multi‑select), region, policy version, and date range When the user applies them Then all dashboard widgets, tables, exports, and API responses reflect the filters consistently Given a region filter When computing after‑hours, holidays, and work hours Then calculations use the region’s local business hours and holiday calendars from the active policy configuration Given multi‑select teams with overlapping membership When totals are computed Then users are not double‑counted across metrics that count unique users
Audit‑Ready Reports and Exports
Given selected filters and the report type "Audit Report" When the user generates the report Then a PDF is produced containing: header metadata (org, teams, regions, date range, policy versions, generated_at, reporting time zone), metric summaries, and trend charts for meeting volume, focus time gained, time debt accrued/paid, override rate, and normalization timeline Given a data export request When the user downloads the export Then CSV and JSON files are available with a documented schema_version, ISO 8601 timestamps with offsets, and include traceability fields (cooldown_id, policy_version_id, team_id, region, user_id/hash) Given a generated audit report When reviewed Then it includes page numbers and a retention notice indicating the active data retention window
Analytics API Access
Given a client with a valid OAuth2 token with scope analytics:read When it calls GET /api/analytics/cooldown with filters matching the dashboard Then the API returns HTTP 200 with metric values equal to the dashboard for identical filters Given large result sets of underlying events When calling GET /api/analytics/cooldown/events Then results are paginated with a stable cursor, limit parameter support, and next cursor until exhaustion Given a client without valid credentials or insufficient scope When calling analytics endpoints Then the API returns 401 or 403 without leaking metric values Given the OpenAPI specification endpoint When requesting /api/openapi.json Then the analytics endpoints, parameters, and response schemas are documented
Data Retention Controls and Purge
Given an org admin sets analytics data retention to N days When N days elapse for stored events Then data older than N days is excluded from dashboards, exports, and API responses and is purged per policy Given a scheduled purge job completes When the job finishes Then an audit log entry records org_id, retention_days, deleted_time_range, count_deleted, job_id, and completed_at Given an audit report generated while retention is active When the report is created Then the report displays a notice of the retention window and indicates that metrics may exclude data prior to the window
Override Rate and Normalization Timeline Calculations
Given recorded override events with reason codes and prevented after‑hours conflicts When calculating override rate Then override rate = overrides / (overrides + prevented_conflicts) × 100, rounded to one decimal place, scoped to the selected filters Given a cooldown start date and a pre‑cooldown baseline When calculating normalization Then normalization timeline equals the number of days from cooldown start until meeting volume and after‑hours invitations return to within 5% of the pre‑cooldown baseline for 7 consecutive days Given the normalization metric is expanded When the user opens the breakdown Then a daily series is shown with the detected normalization date highlighted and underlying counts available for export

SLA Triggers

Tie surge activation to objective signals—SLA breaches, incident priority, quota depletion, or launch milestones. Auto‑compose an approval request with context, scope, and expiry, so surges start quickly when warranted and stay auditable when they don’t.

Requirements

Signal Integrations for SLA Inputs
"As an engineering manager, I want Timeglue to ingest SLA and incident signals from my tooling so that surge decisions are based on objective, real-time data instead of manual judgment."
Description

Provide first-class integrations that ingest objective signals from external systems (e.g., PagerDuty/Opsgenie incidents, Datadog/New Relic SLO/SLA breaches, Statuspage service status, Zendesk/Jira ticket SLAs, usage/quota systems, and product launch milestones). Support webhooks and polling with OAuth and signed secrets, field mapping, and normalization into a unified signal schema (severity, impacted service, SLA dimension, quota remaining, milestone date). Ensure tenant-scoped encrypted credentials, idempotency and de-duplication, exponential backoff, rate limiting, and integration health checks. Offer an admin UI to add connectors, test payloads, and map attributes to Timeglue entities (teams, regions). This enables objective, low-latency triggers, reduces manual overhead, and forms the foundation for auditable surge activations in Timeglue.

Acceptance Criteria
Tenant-Scoped OAuth and Secret Management
Given an admin of tenant X adds a connector using OAuth 2.0 When the OAuth flow completes Then access and refresh tokens are stored encrypted, scoped to tenant X, and are not accessible to other tenants or services And tokens are auto-rotated prior to expiry without interrupting ingestion Given a webhook signing secret or API key is entered in the connector settings When the admin saves the configuration Then the secret is encrypted at rest, never displayed in plaintext after save, and masked in all UI and logs Given a user without the Admin role attempts to view or export connector credentials When they access the connector settings Then the credentials are not displayed and an authorization error is shown And an audit log entry records the denied access attempt Given the admin initiates credential rotation for a connector When rotation completes Then the old credentials are invalidated and the new credentials are used for subsequent requests without data loss
Secure Webhook Ingestion with Signature Verification
Given a connector is configured with a webhook endpoint and signing secret When the provider sends a webhook with a valid signature and a timestamp within the allowed clock skew window Then the request is accepted (2xx), verified, and the payload is queued for processing Given a webhook arrives with an invalid signature or a timestamp outside the configured skew window When verification is attempted Then the request is rejected (4xx), logged with reason, and no processing occurs Given the provider retries delivery of the same event with the same unique event ID within the de-duplication window When the webhook is received Then it is acknowledged (2xx) but not processed again Given a transient processing failure occurs after signature verification When the system attempts to process the payload Then the request is marked for retry with exponential backoff up to the configured max attempts and detailed failure telemetry is captured
Polling Connector with Rate Limiting and Backoff
Given a polling connector is configured for a provider API with an OAuth token When the scheduler runs Then API requests are executed at or below the configured per-connector and per-tenant rate limits Given the provider returns HTTP 429 with a Retry-After header When subsequent polling attempts are scheduled Then the connector respects Retry-After and applies exponential backoff without exceeding the maximum backoff interval Given the provider returns HTTP 5xx or a network error occurs When retrying requests Then the connector retries with exponential backoff up to the configured attempt limit and surfaces a degraded health status if the limit is reached Given the connector resumes after backoff When the next window is polled Then no events are missed within the provider’s lookback window and no duplicates are emitted
Field Mapping and Unified Signal Normalization
Given a connector receives a payload containing provider-specific attributes When the configured field mapping is applied Then a normalized signal is produced with severity (enum), impactedService (ID), slaDimension (enum), quotaRemaining (number), milestoneDate (ISO8601), source, sourceEventId, occurredAt (UTC), and receivedAt (UTC) Given required mappings are incomplete or invalid When a payload is processed Then normalization fails with a clear validation error, the event is put into a mapping_error queue, and the admin UI displays the failure with field-level details Given attribute mappings to Timeglue teams and regions are configured When a payload includes corresponding provider attributes Then the normalized signal includes teamId and regionId set to the correct Timeglue entities Given the admin updates a mapping When subsequent payloads arrive Then they use the new mapping, while previously normalized records remain unchanged and traceable to the mapping version used Given timestamps are present in the provider payload When normalization occurs Then all timestamps are converted to UTC ISO8601 with millisecond precision
Admin UI: Add Connector, Test Payload, Map Attributes
Given an admin navigates to Integrations and selects Add Connector When they choose a provider and complete required authentication and configuration fields (webhook or polling) Then the connector can be saved only when all required fields validate, with inline errors for missing or invalid entries Given the admin opens Test Payload for a connector When they paste a sample payload and click Test Then the UI displays parsed fields, applied mappings, and the resulting normalized signal, or shows precise validation errors if parsing/mapping fails Given the admin saves attribute mappings to teams and regions When the configuration is saved Then the mappings persist, take effect for subsequent events, and an audit log records who changed what and when Given the admin views a connector details page When the page loads Then it displays current health status, last success time, last error (if any), recent event throughput, and error rate for the last 60 minutes
Idempotency and De-duplication of Incoming Signals
Given multiple deliveries of the same source event (same source, sourceEventId, and occurredAt) arrive within the configured de-duplication window When events are processed Then exactly one normalized signal record is stored and subsequent duplicates are acknowledged without reprocessing Given two provider payloads are semantically identical but differ in non-essential ordering or whitespace When idempotency keys are computed Then the system derives the same idempotency key using a stable canonical hash of the payload and source identifiers Given an event arrives outside the de-duplication window When processing occurs Then it is treated as a new event and processed once Given a retry after partial failure occurs When the same event is retried Then processing is idempotent across all side effects, including storage, mapping, and downstream notifications
Integration Health Checks and Status Reporting
Given a connector is configured When periodic health checks run Then the system evaluates authentication validity, recent ingestion success rate, error rate, and last-seen time to derive a status of Healthy, Degraded, Auth Error, or Stale Given the connector experiences consecutive failures exceeding the threshold within a 15-minute window When status is recalculated Then the status becomes Degraded and the reason and last failure details are visible in the admin UI and via the Integrations API Given the connector’s credentials are revoked or expired When a request fails with an authentication error Then the status becomes Auth Error and the admin UI surfaces remediation steps to re-authenticate Given a webhook connector receives no events for longer than the expected interval configured for that provider When the health check runs Then the status becomes Stale and the last-seen timestamp is displayed along with a recommendation to trigger a provider test event
Trigger Rule Builder
"As a team lead, I want to define rules that translate signals into surge requests so that the right surge activates only when warranted and within policy."
Description

Deliver a configurable rules engine and UI that translates normalized signals into surge intents. Allow admins to compose rules using boolean logic and thresholds (e.g., incident.severity >= P1 AND region = EMEA AND during business days), time windows, cool-down/cancel conditions, deduplication windows, and TTL/expiry for surges. Support regional/tenant scopes, rule versioning with change history, dry-run simulation against historical signals, and validation with conflict detection. Provide APIs for CI-managed rules and guard for invalid combinations. This makes surge activation precise, predictable, and testable while aligning with organizational policy.

Acceptance Criteria
Compose rule with boolean logic, thresholds, and time windows
- Given an admin with Rule Builder access, When they define a rule using AND/OR/NOT and comparison operators (>=, <=, =, !=, IN, BETWEEN, EXISTS) on normalized fields (e.g., incident.severity >= "P1", region IN ["EMEA"], business_day = true) and specify a local time window (09:00–18:00), Then the rule validates and saves successfully with a unique rule_id and enabled=false by default. - Given the saved rule is enabled, When a live signal satisfies the rule conditions within the defined time window, Then a single Surge Intent is created containing rule_id, tenant/region scope, composed_context, created_at, and expiry parameters populated. - Given a live signal does not meet at least one condition, When evaluated, Then no Surge Intent is created and the evaluation is logged with reason=no_match.
Deduplication window, cooldown, and cancel conditions
- Given dedup_window = 30m keyed by (tenant_id, region, rule_id), When multiple matching signals occur within 30m, Then only the first creates a Surge Intent and subsequent matches are logged with reason=deduplicated. - Given cooldown = 2h, When a surge ends, Then matching signals within 2h do not create new surges and are logged with reason=cooldown_suppressed. - Given a cancel_condition (e.g., incident.status = "resolved"), When it becomes true during an active surge, Then the surge transitions to cancelled within 1 minute and an audit entry with cancel_reason=condition_met is recorded.
Surge TTL and expiry enforcement
- Given TTL=4h on surges created by the rule, When a surge is created, Then expiry_at = created_at + 4h is set and visible via UI and API. - Given the surge reaches expiry_at and no cancel condition has fired, When the scheduler runs, Then the surge status transitions to expired and no further actions are taken. - Given an authorized user extends TTL to 6h, When saved, Then expiry_at updates immediately and the history log records who, when, and the TTL change.
Regional and tenant scoping of rules
- Given a rule scoped to tenant_id=T1 and regions=["EMEA"], When evaluated, Then only signals with tenant_id=T1 and region in ["EMEA"] are considered; other signals are ignored. - Given a user from tenant_id=T2, When listing rules, Then they cannot view or modify rules belonging to T1. - Given a rule with regions limited to ["EMEA"], When a signal from APAC arrives, Then it does not match even if other conditions are true.
Rule versioning with change history and rollback
- Given an enabled rule at version=1, When a modification is saved, Then version increments to 2, version 1 is immutable, and change history records who, when, and a structured diff of the logic. - Given a rollback to version=1, When executed, Then a new version=3 is created containing version 1’s logic and becomes the latest draft; enabling it routes evaluation to version=3. - Given concurrent edits, When a stale client submits changes, Then the save is rejected with a version_conflict error and requires resubmission against the latest version.
Dry-run simulation against historical signals
- Given a draft or disabled rule and a historical time range, When the admin runs Simulation, Then the system evaluates historical signals and returns: total_events_scanned, matches_count, first_match_at, last_match_at, and up to 100 example match IDs; no surges or notifications are emitted. - Given identical inputs (rule version, time range, filters), When Simulation is rerun, Then results are deterministic and identical. - Given an invalid rule, When Simulation is requested, Then execution is blocked and validation errors are returned without scanning.
Validation, conflict detection, and CI-managed API guardrails
- Given a new enabled rule whose conditions overlap an existing enabled rule for the same tenant and scope, When saving, Then the system detects the overlap and requires explicit resolution (set priority/precedence or mark as mutually exclusive) before enabling. - Given invalid combinations (e.g., TTL < dedup_window, missing scope, cancel_condition contradicts time window), When saving via UI or API, Then the request is rejected with 400-level errors containing machine-readable codes and field-level messages. - Given CI-managed rules submitted via API, When an authenticated service performs an idempotent upsert with rule_id and version tag, Then the API validates schema and guards, supports dry_run=true to validate without persisting, preserves change history, and returns an ETag for drift detection.
Auto-Generated Approval Packet
"As an approver, I want an auto-composed packet with context, scope, and expiry so that I can make a fast, informed decision without digging through multiple tools."
Description

When a rule matches, automatically compose a structured approval request containing trigger context (signal details, timestamps, severity), proposed surge scope (teams/regions, window expansions by local time, duration and expiry), schedule diff preview versus baseline, estimated capacity impact and budget burn, conflict analysis (focus blocks, holidays, existing surges), and risk notes. Render consistently across in-app, email, and Slack with actionable Approve/Deny links and a one-click schedule preview. Include suggested approvers based on policy and on-call rosters. This accelerates informed decision-making and preserves clarity and auditability.

Acceptance Criteria
Trigger-to-Packet Latency and Idempotency
Given an active SLA-trigger rule and a matching SLA breach event with a correlation_id When the event is ingested by the SLA Triggers service Then a new approval packet record with a unique packet_id is created within 60 seconds And the packet initial status is "Pending Approval" And duplicate events sharing the same correlation_id within a 5-minute window do not create additional packets (idempotent) And a metric is emitted confirming packet creation latency and idempotency outcome
Packet Content Completeness and Structure
Given a matched rule with available trigger context and surge policy When the approval packet is composed Then the packet includes sections: Trigger Context (signal id/source, timestamps, severity), Proposed Surge Scope (teams, regions, window expansions by local time, duration, expiry), Schedule Diff Preview vs baseline, Estimated Capacity Impact (FTE-hours and percentage) and Budget Burn (currency), Conflict Analysis (focus blocks, holidays, existing surges), and Risk Notes And each section is populated or explicitly marked "Not Available" with a reason code And numeric fields include units and currency codes; timestamps include timezone offsets And the packet JSON conforms to schema version v1 and passes validation with no errors
Schedule Diff Preview Accuracy
Given a baseline schedule for the impacted teams and a proposed surge scope When the schedule diff preview is generated Then added or expanded windows are shown with per-region start/end times and capacity deltas And the sum of added hours per team/region reconciles to the total capacity delta within ±1% tolerance And conflicts (overlaps with existing shifts) are highlighted and counted And the preview references the exact baseline snapshot id used for comparison
Timezone-Localized Window Expansions
Given impacted regions spanning multiple time zones and potential DST transitions When window expansions are computed Then each expansion is displayed in the affected region’s local time with timezone abbreviation And UTC equivalents are provided in metadata for audit And DST transitions are handled correctly with no missing or duplicated hour miscalculations And no region displays another region’s local time values
Multi-Channel Rendering with Actionable Controls
Given an approval packet ready for distribution When it is rendered in-app, email, and Slack Then section order, titles, and key values are consistent across channels per the rendering template And Approve and Deny actions are present and functional in all channels And clicking Approve records decision, approver identity, optional comment, and transitions packet to Approved; Deny transitions to Denied And a View Schedule control opens a one-click preview showing the packet’s specific diff context And if a channel lacks an interactive surface, equivalent signed links are included
Suggested Approvers from Policy and On-Call Rosters
Given approval policies mapping teams/regions to approver groups and current on-call rosters When the packet is composed Then the Suggested Approvers list includes at least one primary and one fallback approver resolved from policy ∩ on-call And users marked out-of-office or exceeding approval limits are excluded with reason codes And if no approver is resolvable, an escalation group is added and the packet is marked Needs Routing And each suggested approver entry includes role, priority rank, and resolution reason
Auditability, Versioning, and Secure Link Expiry
Given an approval packet and subsequent user interactions When any Approve/Deny action, comment, or link access occurs Then an immutable audit log entry is recorded with packet_id, version, timestamp, actor, channel, action, and before/after status And signed action links are single-use, scoped to packet_id and action, and expire per policy (default 24 hours); expired or reused links do not execute actions and return an informative message And if the packet is recomposed, a new version is created, prior versions remain accessible for audit, and the latest version is presented by default
Approval Workflow & Escalation
"As an incident commander, I want approvals to route and escalate automatically with timeouts so that surges begin quickly during critical events without getting stuck."
Description

Implement policy-driven approval workflows supporting single or multi-step approvals, sequential or parallel routing, timeouts with escalation paths, and emergency auto-approval for P0 incidents with post-facto review. Enforce quorum rules, capture rationale notes, and log timestamps and approver identities. Integrate with Slack and email for notifications and actions, and honor RBAC for who can approve which scope. Define SLAs for approval steps with reminders and fallback behaviors. This ensures surges start quickly when warranted and remain controlled and auditable.

Acceptance Criteria
Sequential Multi-Step Approval Routing
Given a surge request configured with two sequential steps: Team Lead then Director When the Team Lead approves Then the workflow advances to the Director step and remains pending until the Director decides And the Director cannot act before the Team Lead decision is recorded And if any step issues a Reject, the workflow status becomes Rejected and remaining steps are canceled And each step records decision, approver identity, timestamp, and rationale note
Parallel Approval with Quorum
Given an approval step "Security Review" with approvers A, B, C and a quorum of 2 approvals When any two distinct authorized approvers approve Then the step status becomes Approved and the workflow advances When any authorized approver rejects before quorum is met Then the step status becomes Rejected and the workflow stops And duplicate approvals by the same user are prevented And actions by non-authorized users are blocked and logged And quorum count, approver identities, and timestamps are recorded
Timeout Escalation and Fallback Behaviors
Given an approval step with an SLA of 2 hours, reminders at 60 and 90 minutes, and escalation to VP on SLA breach When the step remains undecided at 60 and 90 minutes Then reminders are sent to all pending approvers via Slack and email When the 2-hour SLA expires without a decision Then the VP is notified and added as an escalated approver, and the escalation event is logged with timestamp And if no decision occurs within 1 additional hour, apply the configured fallback (Auto-Approve or Auto-Reject) and log the fallback action and reason And all notifications include request context (scope, reason, expiry) and are traceable in the audit log
Emergency Auto-Approval for P0 Incidents
Given a surge request linked to an incident marked Priority P0 and emergency policy enabled When the request is submitted Then the workflow auto-approves immediately and activates the surge And a post-facto review task is created with an owner (Incident Commander) and due date within 24 hours And failure to complete the review by due date sends alerts to the compliance channel and requestor And the auto-approval records incident ID, submitter, timestamp, and required rationale/context fields
RBAC Enforcement for Approval Actions
Given a user who lacks approval permission for the request scope When the user attempts to approve or reject via UI, Slack, or email link Then the action is blocked with a 403-equivalent error and explanatory message And the attempt is logged with user identity, channel, timestamp, and reason And actionable controls (buttons/links) are only rendered for authorized users And role/permission changes take effect within 60 seconds for new actions
Slack and Email Actionable Notifications
Given an approval step awaiting decision When notifications are sent Then Slack messages include context (request title, scope, reason, SLA/expiry) and Approve/Reject buttons And email notifications include unique, signed action links that expire on step completion or after 24 hours, whichever comes first When an authorized approver approves or rejects via Slack button or email link Then the decision is applied, the message/thread updates to reflect the outcome, and the workflow advances accordingly And the action is idempotent (repeated clicks after decision are ignored with an informative message) and fully audited (who, when, channel)
Audit Trail and Rationale Capture
Given any approval decision (including auto-approval) When the decision is recorded Then a rationale note is required for human approvals and the incident context is captured for emergency auto-approvals And the system records: request ID, step ID, decision, approver identity and role, timestamp (UTC), channel (UI/Slack/email/API), and pre/post state diff And audit entries are immutable, tamper-evident, and filterable by request, approver, decision, and date range And audit data can be exported as CSV and JSON on demand
Surge Schedule Application & Expiry
"As a scheduling admin, I want approved surges to automatically adjust availability and then revert at expiry so that capacity is available when needed without leaving lingering changes."
Description

On approval, apply a timeboxed surge to the scheduling engine: temporarily expand acceptable meeting windows for targeted teams/regions, update availability on smart links, and enforce caps (max meetings per person/day, per-week limits). Respect baseline work hours and holidays unless an explicit override flag is set. Support overlapping surges with priority and merge policies, and automatically roll back at expiry with full cleanup and notifications. Propagate changes in real time to booking links, APIs, and calendar sync, with safeguards to prevent after-hours invites beyond the approved scope.

Acceptance Criteria
Apply timeboxed surge to targeted teams/regions on approval
Given a surge is approved with startTime, endTime, targetTeamsOrRegions, expansionPolicy, and priority When currentTime >= startTime and currentTime < endTime Then the scheduling engine applies the surge only to the specified teams/regions and marks the surge state Active with approver, scope, and expiry persisted Given a surge is Active When acceptable meeting windows are requested for an affected team/region Then returned windows are expanded per expansionPolicy within the timebox and include surgeId and surgeActive=true in API and UI responses Given currentTime < startTime for an approved surge When availability is requested Then no surge changes are applied
Respect baseline work hours and holidays unless override flag is set
Given a surge with overrideAfterHours=false When availability is computed during the surge window Then no slots are offered outside each member’s baseline work hours and no slots are offered on their configured holidays Given a surge with overrideAfterHours=true When availability is computed during the surge window Then slots may be offered outside baseline hours and on holidays only within the surge expansionPolicy scope and all such slots are tagged afterHours=true Given overrideAfterHours=false When a booking attempt targets a time outside baseline hours or on a holiday during the surge Then the booking is rejected with errorCode=SURGE_SCOPE_VIOLATION and no calendar event is created
Enforce surge caps for max meetings per person/day and per week
Given a surge defines caps maxPerPersonPerDay and maxPerPersonPerWeek When a booking is created under the surge Then the system increments surge booking counters per person in their local timezone day/week buckets Given a person has reached maxPerPersonPerDay during the surge When additional bookings are attempted for that person on the same local day Then availability for that person is hidden and booking attempts are rejected with errorCode=SURGE_CAP_EXCEEDED Given a person has reached maxPerPersonPerWeek during the surge When additional bookings are attempted in the same local week Then booking attempts are rejected with errorCode=SURGE_CAP_EXCEEDED and the weekly cap resets at the start of the next local week
Handle overlapping surges with priority and merge policies
Given two or more surges overlap for the same target with defined priority (higher value wins) and mergePolicy in {Override, Union, Intersection} When computing acceptable windows during the overlap Then - If mergePolicy=Override for the higher-priority surge, the higher-priority surge’s expansion and caps fully apply - If mergePolicy=Union, the union of expanded windows applies and caps are the stricter (min) of overlapping caps - If mergePolicy=Intersection, only the intersection of expanded windows applies and caps are the stricter (min) Given two overlapping surges have equal priority When computing effective policy Then the deterministic tiebreaker lastApprovedAt (newest wins) is applied and the decision is logged with effectiveSurgeId
Propagate surge changes to smart links, APIs, and calendar sync in real time
Given a surge is approved, updated, or cancelled When the change is committed Then affected smart links and embedded widgets reflect new availability within 10 seconds and display surge-adjusted slots Given a surge is Active When GET /availability is called for affected teams/regions Then responses include updated windows and a surgeContext object { surgeId, active:true, expiresAt } Given a surge change occurs When outbound calendar sync runs Then created/updated holds or buffer events reflect surge windows within 60 seconds without duplicating existing holds
Automatic rollback at expiry with cleanup and notifications
Given a surge has an endTime When currentTime >= endTime Then the system reverts acceptable windows to baseline within 60 seconds, removes temporary caps, clears surge-specific holds/banners, and marks the surge state Expired Given a surge expires When viewing audit logs Then an entry exists with surgeId, expiredAt, affected targets count, and cleanup results Given a surge expires When notifications are sent Then owners of affected teams receive a summary via preferred channel (email or Slack) with effective period, utilization, and confirmation that baseline availability is restored
Safeguards preventing after-hours invites beyond approved surge scope
Given a booking attempt (UI or API) targets a time outside the effective surge-adjusted acceptable windows When the attempt is submitted Then the system rejects it with HTTP 403 and errorCode=SURGE_SCOPE_VIOLATION and no invitations are sent Given a malicious user tampers with smart link parameters to request hidden time ranges When availability is fetched Then the backend enforces server-side surge scope and returns only permissible slots Given a surge spans a DST transition When acceptable windows are computed Then window boundaries are calculated in each participant’s local timezone with correct DST offsets and no slot is offered outside the approved scope
Audit Trail & Compliance Reporting
"As a compliance officer, I want a complete, immutable audit trail of triggers, approvals, and schedule changes so that we can pass audits and perform accurate post-incident reviews."
Description

Record an immutable sequence of events for each trigger: incoming signal payloads (with selective redaction), evaluated rules and outcomes, approval steps and rationale, schedule changes, notifications, and expiry/rollback. Store with tamper-evident hashing, retention policies, and data residency controls. Provide searchable in-app views, CSV/JSON export, and webhooks for SIEM ingestion. Offer canned reports by date range, team, incident, and surge reason to satisfy audits and post-incident reviews while preserving privacy and provenance.

Acceptance Criteria
Immutable Event Chain for Triggers
Given a new SLA trigger event is recorded When the event is persisted Then an append-only entry is created with timestamp (UTC ISO-8601), actor/service ID, event type, payload_hash, and prev_hash linking to the prior event in the same trigger stream And a verification endpoint returns valid for the chain including the new event Given an attempt is made to update or delete an existing audit event When the write is attempted Then the system rejects the operation and creates a tamper_attempt event with actor, reason, and timestamp And chain verification for the stream remains valid Given multiple events arrive concurrently for the same trigger When they are persisted Then they are assigned a monotonic sequence number with no gaps per trigger_id And out-of-order arrivals are reordered by sequence to maintain a verifiable chain
Selective Redaction of Sensitive Payload Fields
Given an incoming signal payload contains fields tagged as sensitive by policy (e.g., secrets, personal data) When the payload is ingested Then the stored audit event redacts sensitive values (value replaced with [REDACTED]) and stores a salted_hash for comparison And full values are never written to persistent storage or search index Given a user with Admin or Auditor role views, searches, or exports audit data When accessing events with redacted fields Then the redaction is consistently applied across UI, CSV/JSON exports, and webhooks And the event includes redaction_policy_version for provenance Given a search is performed on a redacted field value When using the salted_hash Then matching events are returned And searching by the original plaintext value returns no results
Rule Evaluation Trace and Outcome Provenance
Given a trigger evaluation occurs When the rule engine runs Then the audit event stores decision_id, rule_set_id and version, list of matched_rule_ids, inputs_checksum, outcome (Activate/Reject/Defer), and execution_duration_ms And the event links to the originating signal event via correlation_id Given a historical decision_id is queried When retrieving the trace Then all related events (signal, evaluation, approval, activation) are returned within 2 seconds for datasets up to 10k related events And each event shows the hash chain link and signatures for provenance Given the rule set is updated When new evaluations occur Then events reference the new rule_set version And prior events remain immutable and reference their original version
Approval Workflow Auditability
Given a surge approval request is auto-composed When it is routed to approvers Then each step records approver_user_id, auth_context (incl. MFA_passed flag), rationale_text, requested_scope, expiry_at, and timestamps for requested/approved/denied And notifications sent are logged with channel, recipient, and delivery status Given an approval reaches expiry or is revoked When the expiry/rollback occurs Then a rollback event is recorded linking to the original approval decision_id And resulting schedule changes are logged and correlated Given policy requires two approvers When only one approval is recorded Then surge activation does not occur And the audit trail shows state=pending_additional_approval and blocks activation attempts with a policy_violation event
Searchable In-App Audit Views
Given filters for date range, team, incident_id, surge_reason, actor, and event_type When applied in the audit UI Then the result set reflects all filters accurately and displays total_count And 95th percentile query latency is ≤ 2 seconds for up to 100k events in scope Given paginated results with page_size=100 When navigating pages sequentially Then there are no missing or duplicate events across pages And exports respect the same filters and ordering Given a user has access to specific teams only When performing searches Then only events for permitted teams are returned And cross-tenant access is blocked and logged as access_denied
Exports and SIEM Webhook Delivery
Given a filtered audit query When exporting to CSV and JSON Then the export completes within 60 seconds for up to 500k rows And files include schema_version, exported_at, exporter_id, chain_root_hash, and sha256_checksum And sensitive fields remain redacted identically to in-app views Given a SIEM webhook is configured When new audit events are generated Then events are batched and delivered within 60 seconds And each request is signed (HMAC-SHA256 or Ed25519) and includes an idempotency_key And delivery uses at-least-once semantics with 3 retries and exponential backoff; 2xx responses stop retries; failures are logged and alerting is triggered Given the webhook endpoint is unavailable beyond retry limits When retries are exhausted Then events are placed in a dead-letter queue retained for 7 days And an operator can replay by time range with idempotency respected
Retention and Data Residency Enforcement
Given a team retention policy of N days is configured When events exceed N days Then they are purged within 24 hours of expiry And a purge_summary event is recorded with counts and ranges purged And backup copies are pruned within 7 days of expiry Given data residency is configured for region R When persisting audit events and generating exports Then data is stored and processed only in region R And cross-region replication is disabled; any attempted write outside R is blocked and logged Given a legal hold is placed on a set of incidents or teams When retention would otherwise purge events Then held events are excluded from purge And hold metadata (placed_by, reason, scope, start_at, end_at) is recorded and removal occurs within 24 hours of hold release
Guardrails & Budget Controls
"As a department head, I want guardrails like surge budgets, blackout periods, and conflict checks so that we avoid overuse and protect teams’ after-hours time."
Description

Introduce configurable safety limits: per-team surge budgets (hours per week/month), quiet hours and blackout periods (e.g., company holidays), fair-use caps, and soft warnings as thresholds near. Block or require elevated approval when budgets would be exceeded or when surge scope is high impact. Detect conflicts with critical events (all-hands, launches) and surface explicit acknowledges. Provide a dashboard for current surge status, remaining budget, and upcoming expiries. These controls prevent overuse, protect after-hours boundaries, and keep surges sustainable.

Acceptance Criteria
Per‑Team Surge Budget Enforcement & Period Resets
Given Team "Alpha" has a weekly surge budget of 10 hours and a monthly budget of 40 hours, with 9 hours already logged this week And the requester has permission to propose a surge but not to approve over‑budget requests When the requester submits a new surge request for 2 hours within the current week Then the system calculates the projected weekly usage as 11 hours and flags an over‑budget condition And the surge cannot auto‑activate and instead routes an elevated approval request to the designated approvers And the approval request includes team, period (week), current usage, projected usage, overage amount, and approval expiry window And if an approver approves within the expiry window, the surge activates and the team's weekly usage reflects 11 hours with −1 hour remaining And if the approval is rejected or expires, the surge remains blocked and the requester is notified with reason And at the weekly boundary (per team timezone), the weekly budget usage resets to 0 hours used and 10 hours remaining without carryover unless "carryover" is enabled
Threshold Warnings Near Budget Limits
Given a team monthly budget of 40 hours with soft‑warning thresholds at 75% and 90% And current recorded usage is 28 hours (70%) When a surge request for 4 hours is submitted Then the system predicts post‑activation usage of 32 hours (80%) and issues a soft warning to the requester and team leads before activation And the warning appears in‑app and is sent to configured notification channels (email/Slack) And warnings are emitted once per threshold crossing per period, not repeatedly for subsequent actions within the same band And if a later request would move usage to ≥90%, a distinct 90% warning is issued And an audit entry records when and to whom each warning was delivered
Quiet Hours and Blackout Periods Enforcement
Given the organization has quiet hours configured 19:00–07:00 in each member’s local time and a blackout on 2025‑11‑27 (company holiday) When a surge is scheduled that overlaps any member’s quiet hours by ≥15 minutes Then the system blocks auto‑activation and requires elevated approval with an explicit "Quiet hours override" reason And if the surge falls on a blackout date, the system blocks activation entirely unless an Org‑level approver selects "Blackout override" and provides justification And all overrides capture who approved, timestamp, scope, and override expiry, and are visible in the audit log And if not approved within the expiry, the surge is canceled and stakeholders are notified
Conflict Detection with Critical Events and Explicit Acknowledgment
Given a company all‑hands event from 10:00–11:00 UTC and a product launch freeze 2025‑10‑01 00:00–23:59 UTC are synced as critical events When a surge is proposed that overlaps any critical event by ≥10 minutes or falls within a freeze window Then the system surfaces the conflict details (event name, time window, overlap duration) to the requester and approvers And submission requires the requester to check an explicit acknowledgment "I understand this conflicts with [event]" And elevated approval is required if policy "Approvals required on critical conflicts" is enabled And if acknowledgment is not provided, submission is blocked And the conflict acknowledgment and decision are stored in the audit log
High‑Impact Surge Scope Requires Elevated Approval
Given "high impact" is defined as any surge with duration > 8 hours, participants ≥ 5, or incident priority P1/P0 When a surge request meets any high‑impact criterion Then the system auto‑composes an approval request including scope details (duration, participants, priority), budget impact, and proposed approval expiry And the surge remains in "Pending approval" until approved by a role with "High‑impact approver" permission And upon rejection, the requester receives structured feedback including which high‑impact rule(s) were triggered And upon approval, the surge activates and the approval artifact is attached to the surge record for audit
Fair‑Use Caps for Individual Contributors
Given the team has a fair‑use cap of 6 surge hours per contributor per week And a contributor has already logged 6 hours this week When they are assigned to a new surge totaling 2 hours Then the system warns the requester that the assignee exceeds the fair‑use cap and suggests eligible alternatives And assignment requires elevated approval or reassignment before activation And if approved, the over‑cap assignment is recorded with justification and counted toward the contributor’s weekly cap And at the weekly boundary, individual caps reset in the configured team timezone
Dashboard: Current Surge Status, Remaining Budget, Upcoming Expiries
Given the Guardrails dashboard is enabled for team leads When a team lead opens the dashboard for a selected team Then it displays: current active surges (count and details), weekly and monthly budget totals, used, remaining, and projected usage based on pending requests And it shows upcoming approval expiries and budget period boundaries within the next 14 days And all figures reflect backend state within 60 seconds (P95) of a change And the dashboard supports filtering by team and period (week/month) and exporting the current view And totals reconcile to the sum of underlying surge records to within 0.1 hours precision

Surge Dossier

An auto‑generated audit pack for each surge: who approved, what expanded, who was affected, exception reasons, legal checks, and the rollback/cooldown plan. Export to CSV/JSON or share a link to build trust with stakeholders and satisfy compliance needs.

Requirements

Auto Dossier Compilation & Versioning
"As a remote team lead, I want the system to auto-generate and version a surge dossier so that I can share accurate, up-to-date details of what changed and why without manual assembly."
Description

Automatically compile a comprehensive, time-stamped dossier for each surge capturing the initiating request, approval chain, scope expansions (hours/regions), impacted calendars/resources, exception reasons, compliance check outcomes, and the rollback/cooldown plan with links to supporting evidence. Snapshot the dossier at key lifecycle events (requested, approved, activated, modified, rolled back, cooldown complete) and maintain immutable version history with diff views. Integrate with Timeglue’s scheduling core to ingest surge metadata, work hours, holidays, and focus blocks, emitting a stable internal schema used by UI, exports, and API consumers.

Acceptance Criteria
Auto-Compilation on Surge Request Creation
Given a new surge request is saved in Timeglue When the request is persisted Then a dossier record is auto-created with a unique ID and ISO 8601 UTC createdAt timestamp within 10 seconds And the dossier includes initiating request ID, requester identity, request channel (UI/API), initial scope (hours and regions), and a placeholder rollback/cooldown plan reference And a link to the original request artifact (URI) is stored; if unavailable, the dossier records null with reason "artifact_unavailable" And the dossier status is set to "requested" and an audit log entry is appended referencing the dossier ID
Approval Chain Capture with Evidence Links
Given an approval action occurs When an approver approves or rejects via UI or API Then the dossier records approver name, unique user ID, role, method (UI/API), decision, and ISO 8601 UTC timestamp And the dossier stores a cryptographic approval signature or hash token supplied by the auth service and a link to supporting policy/evidence And the dossier status changes to "approved" on approval and a new version snapshot is created within 10 seconds And the approval chain order is maintained and viewable in the dossier in chronological sequence
Lifecycle Snapshots, Immutability, and Diff Views
Given key lifecycle events (requested, approved, activated, modified, rolled_back, cooldown_complete) When any event is emitted by the surge engine Then the system creates an immutable snapshot version with monotonically increasing version number and checksum And prior versions remain write-once and cannot be altered; attempts to modify are rejected with HTTP 409 and audit logged And a diff view between any two versions highlights field-level changes (additions, deletions, modifications) for scope, approvals, impacted resources, exceptions, and plans, rendered within 2 seconds for dossiers under 10k impacted resources And each version exposes metadata: version, createdAt (UTC), eventType, actor, and changeSummary
Scope Expansion Tracking and Impacted Resources
Given a surge modification that expands or contracts hours or regions When the change is saved Then the dossier records pre-change and post-change scope and computes deltas (hours added/removed, regions added/removed) And the dossier lists impacted calendars/resources with counts by region and a deterministic sample of up to 50 IDs per region for verification And any exception reasons provided are captured with codes and free-text notes and linked to the change record And the diff view for that version displays scope deltas and impacted counts accurately matching the scheduling core computation
Scheduling Core Metadata Ingestion (Hours/Holidays/Focus Blocks)
Given an activation or modification event When ingesting scheduling metadata Then the dossier stores the work hours, holidays, and focus blocks used for computation, including source, effective date range, and region And timezone normalization is applied; all timestamps are stored as ISO 8601 UTC with original timezone retained in a separate field And conflicts (e.g., holiday overrides, focus block collisions) are recorded with resolution strategy used And ingestion succeeds for at least 95th percentile of events within 10 seconds under nominal load (<= 50 concurrent surges)
Export (CSV/JSON) and Stable Internal Schema for UI/API
Given a request to export a dossier When the user selects CSV or JSON Then the export matches internal schema version "v1" and includes schemaVersion in the payload And JSON exports validate against the published JSON Schema without errors; CSV exports include fixed headers matching the schema field names and consistent column order And the exported file includes every snapshot version and their diffs or references to diffs as per schema And a shareable, signed URL can be generated with expiry and access scope; direct fetch via API returns ETag and Last-Modified headers
Rollback and Cooldown Plan Capture and Completion
Given a rollback is initiated When the rollback event is confirmed Then the dossier records rollback initiator, reason, steps executed, execution timestamps, and affected resources, and creates a snapshot with eventType "rolled_back" And a cooldown plan with start/end timestamps, criteria for completion, and monitoring checks is stored and linked to the rollback snapshot And on cooldown completion, the dossier creates a "cooldown_complete" snapshot, updates status accordingly, and locks surge-related modifications until a new surge is requested And compliance check outcomes (pass/fail, checklist items, legal references) are recorded for rollback and cooldown events with links to evidence artifacts
Immutable Approval & Change Audit Trail
"As a compliance officer, I want an immutable audit trail of approvals and changes so that formal audits can verify governance without relying on manual records."
Description

Record every approval and scope change as append-only events with UTC timestamps, approver identity (via SSO), role, comments, and origin metadata (IP/device), secured with cryptographic hashes to prevent tampering. Support multi-step approvals, override justifications, and chain-of-custody linking between events. Surface the audit trail within the dossier and expose it via API, ensuring traceability from initial request through rollout and rollback.

Acceptance Criteria
Immutable Append-Only Event Ledger
Given an existing surge audit trail with events E1..En When any user attempts to edit or delete an existing event via UI or API Then the request is rejected with 403 Forbidden and an error code audit_event_immutable And no audit event records are modified When a new approval, scope change, rollback, or comment is recorded Then a new event En+1 is appended only (no upserts) And En+1 includes a server-assigned immutable event_id
Cryptographic Hash Chain Integrity
Given an audit trail with last event hash Hn When a new event En+1 is appended Then En+1.previous_hash equals Hn And En+1.hash is deterministically computed over En+1 payload plus previous_hash and stored When any stored event payload is altered or removed Then chain verification fails and the surge integrity_status is set to invalid And the dossier audit view displays an integrity alert for the surge And the audit API returns integrity_status=invalid for the surge When chain verification passes Then integrity_status is valid
Approver Identity via SSO and Role Resolution
Given an approver authenticates via SSO and submits an approval or change Then the event stores approver_id (SSO subject), approver_display_name, approver_email, and approver_role at time of action And the stored identity fields are immutable snapshots even if the user’s role changes later When SSO validation fails or the user is unauthenticated Then the request is rejected with 401 Unauthorized When the event is retrieved via dossier or API Then the same identity fields are returned consistently
Multi-Step Approval Sequencing and Overrides
Given a surge configured for a multi-step approval sequence (e.g., Manager -> Legal) When a later-step approver attempts to approve before all prerequisite steps are complete Then the request is rejected with 409 Conflict and error code approval_sequence_violation When an intermediate step approves successfully Then an Approval event is appended with step_index, required_role, and previous_hash linking to the prior event When a policy exception is flagged for the approval Then a non-empty override_justification of at least 10 characters is required And the event records override_required=true and the justification text
UTC Timestamping and Total Ordering
Given any audit event is recorded Then the event.timestamp is stored in UTC using ISO 8601 format with a trailing Z And events are ordered by timestamp ascending, then by event_id to break ties When events are listed in the dossier or via API Then ordering is stable and consistent across time zones and DST changes And the API returns timestamps in UTC without local offsets
Origin Metadata Capture (IP/Device)
Given an approval or change is submitted via a client or API Then the event records origin_ip (IPv4 or IPv6) and device_info (user agent or client identifier) When origin data is unavailable (e.g., system-initiated) Then origin_ip and device_info are stored as null and source=system is recorded When events are retrieved via API Then origin_ip conforms to standard IP formats and device_info is returned as recorded
Dossier and API Exposure with Filtering and Export
Given a surge with N audit events When a user opens the Surge Dossier audit section Then the list shows all events with timestamp (UTC), approver identity, role, comments/justification, origin metadata, hash, and previous_hash And filters by event_type, role, and date range are available and functional When calling GET /surges/{surge_id}/audit with from, to, type, role, and pagination parameters Then the API returns events ordered, supports pagination (cursor or page/size), and includes integrity_status When exporting to CSV or JSON from the dossier Then the exported files contain the same fields as the API response and preserve hash/previous_hash for integrity verification
Affected Parties Resolution & Privacy Controls
"As a stakeholder, I want to understand who is affected by a surge in a privacy-safe way so that I can assess impact without exposing unnecessary personal data."
Description

Resolve and list all impacted users, teams, and external invitees resulting from surge window changes, including counts by region/time zone and role. Provide privacy-aware views based on viewer permissions: aggregated summaries for external stakeholders and identifiable lists for authorized internal roles. Respect DND/consent flags, apply redaction where required, and link to notification delivery logs. Store only minimal identifiers necessary to meet compliance and reporting needs.

Acceptance Criteria
Affected Parties Resolution & Aggregated Counts
Given a surge window change is saved And the system has users, teams, and external invitees with assigned region, time zone, and role When the Surge Dossier is generated Then the Affected Parties section lists unique impacted users, teams, and external invitees without duplicates And counts by region, time zone, and role are calculated and displayed And the sum of all per-dimension counts equals the total unique impacted identities And team entries include team name and count of impacted members And external invitees are counted separately from internal users
Permission-Based Views and Redaction
Given a viewer with External Stakeholder permission accesses the Surge Dossier When viewing Affected Parties Then only aggregated counts by region, time zone, and role are visible And no personal identifiers (name, email, user ID) are displayed And exports are limited to aggregate-only CSV/JSON Given a viewer with Authorized Internal role and PII permission accesses the Surge Dossier When viewing Affected Parties Then identifiable lists include only minimal identifiers: display name (or team name), role, region, time zone, and internal user key And sensitive fields (email, phone) are hidden unless explicitly whitelisted by policy
DND and Consent Flag Enforcement
Given impacted parties include users with Do Not Disturb (DND) or no-notification consent flags When the Surge Dossier is generated Then users with DND/no-consent are excluded from notification targeting counts And they appear in aggregated impact counts And in identifiable views for roles without PII permission, their identifiers are replaced with "Redacted" And any attempt to reveal identifiers requires PII permission and is auditable
Notification Delivery Log Linking and Visibility
Given notification jobs are executed for the surge change When viewing an identifiable affected party entry as an Authorized Internal viewer Then a link to that party's notification delivery log is available And the log displays channel, timestamp, attempt status, and message template ID And for External Stakeholder viewers, only aggregate delivery metrics (sent, failed, pending) are shown with no per-party links
Data Minimization and Storage Controls
Given the system persists Surge Dossier data for affected parties When inspecting stored fields Then only the following identifiers are stored: internal user key (or external invitee hash), role, region, time zone, DND flag, consent flag, and notification_log_id And no plaintext emails or phone numbers are stored in the dossier persistence layer And any access to PII is performed via just-in-time lookup through authorized services and is audited
Deterministic Counting Rules for Multi-Affiliations
Given users may belong to multiple teams, have multiple roles, or multiple calendar time zones When computing counts for the Surge Dossier Then each user is counted exactly once toward total unique impacted users using their primary team, primary role, and home time zone from the profile snapshot at generation time And team-level counts reflect membership impact across all teams a user belongs to, while not inflating the total unique user count And the dossier displays the counting rules used and the profile snapshot timestamp
Exception Reason Taxonomy & Capture
"As an operations manager, I want consistent exception reasons recorded for surges so that I can analyze patterns and justify deviations from policy."
Description

Offer a configurable taxonomy of exception reasons (e.g., critical incident, client escalation, product launch) and require selection plus optional free-text context whenever hours are expanded or constraints overridden. Support localization, rule-based prompts (e.g., when after-hours detected), and analytics tagging to enable cross-surge reporting. Persist reasons in the dossier, display them in the UI, and include them in exports and APIs.

Acceptance Criteria
Enforce Exception Reason Selection on Schedule Expansion
Given a user expands work hours or overrides scheduling constraints for a surge, When they attempt to save or send, Then the system blocks submission until a reason is selected from the active taxonomy. Given the exception dialog is open, When the user selects a reason and optionally enters context, Then the system accepts up to 500 characters of free text and trims leading/trailing whitespace. Given a reason is selected, When the change is saved, Then the dossier records reason_code, reason_label, context_text, actor_id, actor_role, surge_id, affected_entities, and timestamp (ISO 8601 UTC). Given a save is in progress and the network fails, When the user returns to the form within 15 minutes, Then the previously entered reason selection and context are restored from local draft storage.
Configurable Exception Reason Taxonomy (Admin)
Given an org admin opens Taxonomy settings, When they create a new reason with code, label, category, and locale strings, Then it becomes available immediately to prompts and selection UIs. Given a reason is in use, When an admin attempts to delete it, Then the system prevents deletion and offers deprecate; deprecated reasons are hidden from new selections but remain valid for historical records. Given a reason's label or localized text is edited, When saved, Then historical dossiers retain stored labels while exports include both canonical reason_code and label_at_capture. Given taxonomy changes are saved, When viewing audit logs, Then each change has a version_id, actor_id, change_type, and timestamp.
Rule-Based Prompt for After-Hours/Holiday/Focus Overrides
Given a meeting includes any participant time outside configured work hours, on a holiday, or during a focus block, When the user finalizes scheduling, Then an exception prompt appears with rule_flags indicating after_hours, holiday, and/or focus_conflict. Given multiple participants are affected, When the prompt appears, Then it shows the count and top 3 names with “and N more”, and requires one reason for the surge action. Given the user cancels the prompt, When they return to scheduling, Then no changes are saved and the UI indicates that an exception reason is required to proceed.
Localization of Reason Selection and Prompts
Given a user locale is supported, When the exception dialog opens, Then reason labels and prompt copy display in that locale; missing translations fall back to org default then en-US. Given a right-to-left locale, When the dialog opens, Then layout mirrors and text aligns per locale conventions without truncation. Given a reason is captured, When exporting or via API, Then both canonical reason_code and a localized label for the requester’s locale are provided.
Analytics Tagging and Cross‑Surge Reporting
Given a reason is captured, When save completes, Then an analytics event surge.exception_captured is emitted with org_id, surge_id, reason_code, category, rule_flags, has_context, actor_role, and timestamp. Given dashboards filter by date range, reason_code, or category, When queried, Then counts and percentages match underlying dossiers within ±0.1%. Given a user changes the selected reason before saving, When saved, Then only the final selection is emitted once; no duplicate events occur.
Display in Surge Dossier and Scheduling UI
Given a surge dossier is viewed, When it contains exceptions, Then each entry shows reason_label, reason_code, context_text, actor, affected time windows, and rule_flags. Given a dossier with no exceptions, When viewed, Then the Exceptions section displays “No exceptions recorded”. Given a user lacks edit permissions, When viewing exceptions, Then details are visible read-only and no edit controls are shown.
Inclusion in CSV/JSON Exports and Public APIs
Given a dossier is exported to CSV, When downloaded, Then it contains columns: surge_id, exception_id, reason_code, reason_label, category, context_text, actor_id, actor_role, created_at (ISO 8601 UTC), rule_flags, locale. Given a dossier is exported to JSON, When downloaded, Then it includes the same fields with correct types and ISO timestamps. Given GET /surges/{id}/exceptions is called with a valid token, When filters reason_code, category, date_from, date_to are applied, Then the API returns paginated results matching the dossier with total_count and next_page cursor.
Compliance & Legal Checks Engine
"As legal counsel, I want automated compliance checks logged in the dossier so that we can prove due diligence and prevent non-compliant surges from going live."
Description

Implement a configurable rule engine that evaluates each surge against work-hour policies, regional labor regulations, holiday restrictions, and contractual SLAs. Produce pass/warn/block outcomes with detailed evidence, including the rule matched, input data, and rationale, and support rule versioning and jurisdiction scoping. Gate surge activation based on severity, require documented overrides with justification, and record all results in the dossier for auditability.

Acceptance Criteria
Overall Outcome Aggregation Logic
Given a surge evaluation that matches zero or more rules When the engine computes the overall outcome Then the overall outcome equals the highest severity among matched rules using the order block > warn > pass And the response includes outcome, matched_rules[], and evaluated_at timestamp And matched_rules[] contains for each rule: rule_id, rule_name, version, severity, jurisdiction, rationale, and input_data_summary
Work-Hour Policy Evaluation
Given team work-hour policies configured per member timezone And a surge proposing meetings across members When the engine evaluates the surge Then any meeting outside a member’s allowed hours produces a rule match with the configured severity (warn or block) And evidence includes member_id, local_date, local_time_window, timezone, policy_window, and delta_minutes And if all meetings are within allowed hours, no work-hour rule is matched and the overall outcome remains unchanged
Regional Labor Regulation Compliance
Given jurisdiction-scoped labor rules (e.g., mandatory rest periods) enabled for regions of affected members And a surge window that intersects a restricted period in any member’s jurisdiction When the engine evaluates the surge Then a block rule match is produced for each violating jurisdiction unless the rule is configured as warn And evidence includes jurisdiction_code, regulation_ref, member_count_affected, and violating_time_windows And activation is gated (cannot proceed) while any block rule remains unresolved
Holiday Restriction Check
Given a maintained holiday calendar per jurisdiction And a surge includes meetings on a public holiday for any affected member where policy is 'No Meetings' (block) or 'Discouraged' (warn) When the engine evaluates the surge Then the engine emits corresponding rule matches with the configured severity And evidence includes holiday_name, holiday_date, jurisdiction_code, affected_member_ids, and policy_setting And if all affected jurisdictions allow meetings on that holiday, no holiday rule is matched
SLA Compliance Evaluation
Given contractual SLA rules configured for a client or project (e.g., max N after-hours meetings per month, response window thresholds) And historical usage data and the proposed surge schedule When the engine evaluates the surge Then the engine computes projected SLA impact and emits a rule match if the surge would breach or risk breaching an SLA And block is used for definite breach, warn for risk threshold, pass otherwise And evidence includes sla_id, metric_name, threshold, current_value, projected_value, calculation_window, and rationale
Override Workflow and Activation Gate
Given an evaluation outcome containing at least one warn or block rule match When a user with role ComplianceApprover submits an override Then the override requires a free-text justification of at least 20 characters and captures approver_id, timestamp, and referenced rule_ids And block overrides switch the affected rule’s disposition to approved_override and allow activation; warn requires acknowledgement only And all overrides are recorded in the surge dossier with immutable audit fields And if the user lacks the required role or omits justification, the override is rejected and activation remains blocked
Rule Versioning, Jurisdiction Scoping, and Dossier Recording
Given rules with explicit version numbers and jurisdiction scopes When a surge is evaluated Then the engine records for each match the rule version used and the jurisdiction(s) applied And the surge dossier stores a complete evaluation record including outcome, matched_rules[], raw_input_snapshot, and rationale text And when rules are updated to a new version, re-evaluating the same surge stores a new evaluation record without altering prior records And evaluations default to the latest active rule versions unless the surge is locked to a snapshot at approval time per configuration
Export & Share Links with Scoped Access
"As a client stakeholder, I want a secure link or file export of the surge dossier so that I can review and archive details for my organization’s compliance records."
Description

Enable export of full or partial dossiers to CSV and JSON using stable, documented schemas, and provide shareable, expiring links secured with signed tokens. Support role-scoped views (internal, client, auditor), watermarking, and download/audit logs. Ensure exports respect redaction and data retention policies, and offer API retrieval with pagination and ETag support for synchronization.

Acceptance Criteria
Export Full Dossier to CSV and JSON Using Stable Schema
Given a surge dossier exists and the user has export permission When the user requests a full export in CSV and JSON formats Then both files are generated and downloadable within 5 seconds for dossiers with ≤10,000 rows And each file declares schemaVersion matching the latest published schema And field names, types, and order match the published schema documentation And all timestamps are ISO 8601 UTC (e.g., 2025-09-01T12:00:00Z) And CSV is UTF-8 (no BOM), RFC 4180 compliant, comma-delimited, LF line endings, with quoted fields as needed And JSON is UTF-8 and contains a top-level object with metadata and data array And all identifier fields are exported as strings to prevent numeric precision loss And filenames follow pattern timeglue_dossier_<surgeId>_<schemaVersion>_<timestamp>.<ext>
Partial Export with Redaction and Retention Enforcement
Given the user selects specific sections/fields and a date range for export And applicable redaction and data retention policies exist for the dossier When the export is generated Then only the selected sections/fields are included in the output And values subject to redaction are replaced with "[REDACTED]" and include a redactionReason code And records outside the retention window are excluded from the export And export metadata indicates redactionApplied=true and includes retentionPolicyId And no derived field or aggregate reveals redacted values And the export endpoint returns 400 if the requested fields violate policy or scope
Shareable Expiring Link Secured by Signed Token
Given an authorized user creates a share link with a defined role scope, expiration timestamp, and usage limit When a recipient opens the link before expiration and within usage limits Then access is granted only to the scoped view defined at link creation And the link token is signed and tamper-evident, encoding dossierId, role, issuer, expiry, and export parameters And any token tampering or expired token yields 401/403 without leaking sensitive details And up to ±5 minutes clock skew is tolerated for expiry checks And the link owner can revoke the link; subsequent attempts return 410 Gone And one-time links become invalid after the first successful download; multi-use links decrement usage count per access
Role-Scoped Views with Watermarking
Given roles Internal, Client, and Auditor have defined field-level permissions When an export or share link is generated for a specific role Then only fields and sections permitted to that role appear in the output And disallowed sections are omitted entirely (not null-filled) And a watermark including viewer identifier (userId or hashed linkId), role, and generatedAt is embedded And for JSON, watermark appears under metadata.watermark; for CSV, watermark is included as header comment lines prefixed with '#', followed by a blank line before data rows And the watermark is present on every paginated part or file segment And attempts to request elevated scope via client parameters are ignored and logged as policy violations
Download and Access Audit Logging
Given any export creation or shared-link download is initiated When the action succeeds or fails Then an immutable audit event is recorded within 2 seconds containing actor (userId or linkId), surgeId, role, timestamp (UTC), IP, userAgent, format, outcome, and errorCode (if any) And audit events are queryable by administrators via a secure endpoint with pagination and time filters And PII in logs is minimized per policy and never includes export contents And rate limits are enforced at max 5 exports per minute per user; excess attempts return 429 and are logged And shared-link downloads attempt to capture a recipient fingerprint (e.g., hashed email if provided) without making it mandatory
API Retrieval with Pagination and ETag Synchronization
Given a client retrieves a dossier via the API When requesting pages with pageSize ≤ 1000 Then responses are deterministically ordered and include nextPageToken when more data is available And each response includes a strong ETag representing the current view of the resource And subsequent GET with If-None-Match matching the ETag returns 304 Not Modified And page tokens expire after 24 hours and are scoped to the requesting principal And p95 time-to-first-byte is ≤ 1 second for pages ≤ 1 MB under nominal load
Schema Versioning and Backward Compatibility
Given integrators consume schema version N When schema version N+1 is introduced Then exports allow requesting version N for at least 180 days after N+1 release And the requested version is selectable via API and reflected in export metadata (schemaVersion and docsUrl) And backward-incompatible changes only occur with a version bump; new fields are additive And published schema documentation and changelog are updated on release And a schema validation endpoint validates an export against the declared schema version and returns pass/fail with errors
Rollback & Cooldown Plan Builder with Enforcement Hooks
"As a team lead, I want a system-enforced rollback and cooldown plan so that operations reliably return to normal without manual coordination errors."
Description

Provide templates and structured fields to define rollback steps, owners, triggers, and a cooldown schedule following a surge. Integrate with Timeglue’s scheduler to automatically retire surge rules on schedule, block new surges during cooldown, and create reminders/tasks to confirm completion. Track execution status and outcomes, and capture the finalized plan and evidence in the dossier.

Acceptance Criteria
Template-Based Plan Creation
Given an active or scheduled surge exists When a lead opens the Plan Builder and selects a rollback template Then the form pre-populates rollback steps, owners, triggers, and a cooldown schedule matching the template Given the lead edits any field When Save Draft is clicked Then the draft is versioned with timestamp and author and is retrievable within 2 seconds Given required fields are missing (≥1 rollback step with owner, ≥1 trigger, a cooldown schedule, and success criteria) When Publish is clicked Then publishing is blocked and inline errors identify each missing field Given a plan is published When the surge dossier is viewed Then the plan appears under “Rollback & Cooldown Plan” with version tag and publish timestamp
Define Triggers, Owners, and Step Dependencies
Given rollback steps are added When dependencies are set between steps Then the system prevents cyclic dependencies and displays calculated critical path duration Given an owner is assigned to a step When the assignment is saved Then the owner must be a valid Timeglue user or verified email; otherwise an error is shown and save is blocked Given metric- or time-based triggers are defined When the plan is saved Then trigger type, comparator, threshold, and polling schedule are stored in human- and machine-readable form Given a trigger condition becomes true during the surge When detection occurs Then the plan status changes to “Rollback Pending” and all owners receive notifications within 60 seconds
Auto-Retire Surge Rules on Schedule
Given a rollback schedule specifies a retirement time for surge rules When the retirement time arrives Then Timeglue disables all surge-specific scheduler rules across affected calendars within 60 seconds and logs each action Given a rule cannot be disabled due to permissions When enforcement runs Then the system retries up to 3 times over 10 minutes and escalates to the plan owner with an actionable error Given rules are retired successfully When the dossier is viewed Then an audit entry lists rule IDs, timestamps, actor “system”, and outcome “retired”
Cooldown Enforcement Blocking New Surges
Given a cooldown window is defined from publish or retirement time When a user attempts to create or schedule a surge overlapping that window for the same scope (team/project/calendars) Then creation is blocked with a message showing the cooldown end date/time and policy reference Given an approver with override permission supplies an exception reason When the override is approved Then the surge is allowed and the dossier records the approver identity, timestamp, and “Cooldown Override” flag with the reason Given a new surge targets a different scope When it is created Then it is only blocked if scope intersection with the cooled-down scope is non-empty; otherwise it is allowed
Reminders and Tasks for Rollback Confirmation
Given rollback steps have owners and due times When the plan is published Then tasks are created for each step and reminders scheduled at T−24h and T−1h via each owner’s notification preference (email/Slack) Given an owner marks a step Done and uploads evidence When the submission is saved Then the task status updates to Done, evidence is stored with checksum and timestamp, and the dossier links the evidence Given a task is overdue When its due time passes Then the system escalates to the surge approver and posts a summary to the configured channel
Execution Status Tracking and Outcome Metrics
Given rollback steps progress over time When a step changes state Then the plan shows real-time status (Not Started/In Progress/Blocked/Done) and percent complete based on step weights Given outcome metrics and success criteria are defined (e.g., error rate ≤ X) When the cooldown completes Then the system captures a final metric snapshot, compares against criteria, and marks the plan “Succeeded” or “Needs Follow-up” Given a step is marked Blocked with a reason When saved Then the blocker reason is visible in the dossier and included in the daily digest until resolved
Finalization, Evidence Capture, and Dossier Immutability
Given all steps are Done and success criteria are met When the lead clicks Finalize Then the plan becomes read-only, a content hash for plan and evidence is generated, and the dossier records the finalization event Given the dossier is shared via link When an external stakeholder accesses it Then the rollback & cooldown section is viewable with PII redacted per policy and evidence downloads limited to allowed file types Given a finalized plan requires edits When an admin submits an Amendment Then a new version is created, linked to the original, with a full audit trail of changes and approver identity

Domain Snap

Instantly recognizes your company domain and pre-configures the right integrations (Google/Microsoft calendar, Okta/Azure AD, HRIS). Detects default locale patterns to seed work hours and holidays so you’re halfway set up before you click anything.

Requirements

Domain Auto-Detection & Verification
"As an organization admin, I want Timeglue to recognize and verify our company domain automatically so that setup is fast and correctly aligned with our systems."
Description

Automatically detect the company domain from the user’s email or provided URL during onboarding and perform soft and strong verification steps to confirm ownership. Use DNS, MX/SPF, and OpenID tenant discovery to infer whether the organization uses Google Workspace or Microsoft 365 and to identify potential identity providers (Okta, Azure AD, Google Directory). Where possible, leverage admin OAuth consent to confirm tenant-domain binding; otherwise, apply soft checks (MX records, WHOIS, certificate SANs) and flag confidence. Handle edge cases such as vanity subdomains, split delivery, and multiple verified domains per tenant. Provide a clear UI state indicating detection confidence and next steps. This ensures Timeglue can initialize the correct setup path with minimal input and sets a reliable foundation for downstream auto-configuration.

Acceptance Criteria
Onboarding: Email-based company domain auto-detection
1) Given a syntactically valid corporate email is entered, when the user advances onboarding, then the registrable domain is extracted using the Public Suffix List and stored in session within 500 ms. 2) Given plus addressing or tags in the email, when extracting the domain, then tags are ignored and the domain is unaffected. 3) Given the detected domain matches a previously verified organization in the system, when proceeding, then the domain is marked Verified and provider inference is skipped. 4) Given a public webmail domain (e.g., gmail.com, outlook.com, yahoo.com), when detected, then the user is prompted to enter a company URL and auto-configuration is not initiated. 5) Given an invalid or disposable email domain, when detected, then an error message is shown and the Next action is disabled until corrected.
Provider inference via DNS/MX/SPF and OpenID tenant discovery
1) Given a domain, when querying DNS MX and SPF records (3-second overall timeout), then results are cached for 15 minutes and used to infer Google Workspace (MX contains .google.com) or Microsoft 365 (MX/SPF contains outlook.com or protection.outlook.com) with a reason code. 2) Given OpenID configuration discovery for Microsoft at https://login.microsoftonline.com/{domain}/v2.0/.well-known/openid-configuration returns 200, when parsed, then the tenant ID is captured and used to raise Microsoft confidence. 3) Given Google Workspace domains, when querying MX/SPF, then presence of aspmx.l.google.com MX increases Google confidence; absence of both providers yields Provider=Unknown. 4) Given conflicting signals (both Google and Microsoft present), when detected, then Provider=Ambiguous and confidence <= 0.5 is returned with both reasons listed. 5) Given the inference process completes, when successful, then a provider classification and numeric confidence (0.0–1.0) are produced; if no signals available within timeout, then classification=Unknown and confidence=0.0.
Strong verification via admin OAuth consent
1) Given provider inference indicates Google or Microsoft with confidence >= 0.6, when the user selects "Verify as Admin", then the appropriate OAuth consent screen launches with least-privilege scopes and a 2-minute window. 2) Given consent is granted, when the callback is received, then the tenant identifier and verified domains are retrieved and persisted, and the provided domain must match one of the tenant's verified domains for strong verification to pass. 3) Given a mismatch between provided domain and tenant domains, when detected, then strong verification fails with a clear remediation message and no tokens are persisted. 4) Given the OAuth flow errors or times out, when detected, then the user is returned to onboarding with state preserved and a retry option presented. 5) Given successful strong verification, when completed, then the domain status is set to Verified (Strong), provider is locked, and downstream auto-configuration is unblocked.
Soft verification via DNS/WHOIS/Certificate SAN signals
1) Given strong verification is not performed or fails, when soft checks run, then the system evaluates DNS TXT (SPF), MX, WHOIS registrant, and certificate SANs for the apex and returns a numeric confidence score with signal breakdown. 2) Given WHOIS privacy is enabled, when WHOIS data is unavailable, then the soft verification proceeds without that signal and records "WHOIS unavailable" without failing. 3) Given HTTPS certificate discovery on https://{domain} and https://www.{domain}, when at least one endpoint presents a certificate whose SAN includes the domain, then a positive signal is added; if TLS cannot be negotiated within 5 seconds, the signal is skipped. 4) Given soft verification completes, when confidence >= 0.8, then status=Verified (Soft-High); when 0.5–0.79, status=Soft-Medium; when <0.5, status=Soft-Low and admin verification is required before auto-configuration. 5) Given the soft verification result, when displayed, then an explanation includes all signals and their weights with timestamps.
Edge handling: vanity subdomains and split delivery
1) Given an email from a vanity subdomain (e.g., user@team.example.com), when MX for the subdomain is absent but apex MX exists, then the system normalizes to the apex domain for detection and verification. 2) Given split delivery where MX points to a gateway and SPF includes both Google and Microsoft, when detected, then classification=Split Delivery and the system prompts the user to choose the primary calendar/identity provider. 3) Given the user overrides the normalized domain, when entered, then the system re-runs detection and preserves both the subdomain and apex mapping for future lookups. 4) Given subdomain normalization is applied, when strong verification later returns tenant domains including the apex, then the subdomain is associated to the same tenant record.
Multiple verified domains per tenant handling
1) Given a tenant reports multiple verified domains during strong verification, when received, then the UI presents the list with the email-entered domain preselected and allows selection of a different domain. 2) Given the user selects a different verified domain, when confirmed, then the onboarding session updates to the selected domain and stores all domains mapped to the same tenant. 3) Given multiple domains per tenant, when subsequent users onboard with any mapped domain, then the system recognizes and attaches them to the existing organization record. 4) Given multiple domains, when generating smart links or discovery prompts, then the canonical domain is the one selected during onboarding but alternative domains remain recognized.
UI: Detection confidence display and next-step guidance
1) Given any detection/verification outcome, when shown in UI, then a badge displays confidence tier (High/Medium/Low), numeric confidence, and provider classification, all within 300 ms after data is available. 2) Given High confidence (>=0.8 Strong or Soft-High), when present, then the UI enables "Continue" to auto-configuration; for Medium (0.5–0.79), it shows "Verify as Admin" primary action; for Low (<0.5), it disables auto-configuration and provides manual verification options. 3) Given any error state, when presented, then the UI includes actionable next steps, a retry button, and a link to help docs; copy meets readability grade <= 8 and is localized to the user's detected locale. 4) Given accessibility requirements, when rendering the state, then contrast ratio is >= 4.5:1, focus order is logical, and all dynamic updates announce via ARIA live regions.
Integration Recommendation & Auto-Configuration
"As an admin, I want Timeglue to recommend and auto-configure the right calendar, SSO, and HRIS integrations for our domain so that I can start syncing without manual setup."
Description

Based on the verified domain, recommend and preconfigure the correct integrations: Google Calendar or Microsoft 365 Calendar, Okta or Azure AD (or Google Directory) for identity, and leading HRIS options when detected. Preselect least-privilege scopes, generate OAuth consent links, and initialize connector records with inferred tenant IDs and endpoints. After consent, perform connectivity tests, fetch sample metadata (e.g., directory counts, calendars), and schedule initial sync jobs. Surface a single review panel summarizing detected providers, scopes, and what will be synced. Provide robust error handling and fallbacks if multiple providers are detected or a recommended integration fails, guiding the admin to manual selection. This reduces time-to-value by eliminating manual discovery and configuration steps and ensures Timeglue has the data sources needed for scheduling and availability logic.

Acceptance Criteria
Domain Verification Triggers Provider Discovery
Given a company domain has been verified for Timeglue When Domain Snap initializes provider discovery Then the system identifies candidate providers for calendar, identity, and HRIS based on DNS/MX records, well-known endpoints, and public APIs And produces a ranked list per category with a confidence score for each candidate And marks any category with no candidates as "Not Detected" and enables manual selection for that category And persists discovery results to the tenant configuration record
Calendar Integration Recommendation & Least-Privilege Scopes
Given Google Workspace and/or Microsoft 365 are detected as calendar candidates When the highest-confidence calendar provider is selected Then the system recommends that provider and preselects read-only scopes strictly limited to free/busy and calendar list access And excludes create/update/delete event scopes by default And generates an OAuth consent URL containing only the preselected scopes and a tenant-bound state parameter And displays the recommended provider and scopes in the review panel for confirmation
Directory (IdP) Recommendation & Least-Privilege Scopes
Given Okta, Azure AD, and/or Google Directory are detected as identity candidates When a single provider has the highest confidence matching the verified domain Then the system recommends that provider and preselects read-only directory scopes needed for users, groups, and time zone/locale attributes And excludes write or privileged admin scopes not required for availability logic And infers tenant/organization identifiers and API base endpoints for the selected provider And displays the recommendation and scopes in the review panel for confirmation And if confidence is tied between providers, prompts the admin to choose during review with a clear explanation
HRIS Detection & Preconfiguration
Given an HRIS provider is detectable via domain patterns, email headers, SSO integration, or known endpoints When a supported HRIS (e.g., BambooHR, Workday, Rippling, Gusto, HiBob) is identified Then the system recommends the HRIS and preconfigures base API endpoints and inferred tenant identifiers And preselects minimum scopes required to read employee directory, employment status, location, and work hours/holidays where available And if multiple HRIS systems are detected, presents a choice in the review panel with confidence scores And if no HRIS is detected, flags the category as optional with a manual setup path
OAuth Consent Links & Connector Initialization
Given recommended providers have been determined When the admin proceeds to connect Then the system generates provider-specific OAuth consent links with correct redirect URIs, scopes, and CSRF-resistant state values And creates connector records per provider in "Pending Consent" state with stored inferred tenant IDs and endpoints And allows consenting providers independently and in any order And upon consent denial or error, updates the connector state to "Consent Denied" with actionable error details and retry capability
Connectivity Tests, Sample Metadata Fetch, and Initial Sync Scheduling
Given OAuth consent is successfully completed for a provider When the authorization callback is received Then the system exchanges the authorization code for tokens, stores them encrypted, and validates token scopes And performs connectivity tests including token introspection (if available), basic API call health checks, and permission verification And fetches sample metadata (e.g., calendar list count for calendar providers; user and group counts for directories; employee count for HRIS) and stores summary metrics And schedules the initial sync job(s) with created job identifiers and next-run times visible to the admin And if any test fails, marks the connector as "Needs Attention" and provides a guided remediation path
Review Panel Summary, Manual Override, and Fallback Handling
Given provider discovery and any completed consents/tests When the review panel is presented to the admin Then it summarizes detected providers, selected recommendations, inferred tenant IDs and endpoints, scopes to be requested, and what data will be synced And allows the admin to override provider selections and adjust scopes within supported minimum/maximum sets before confirming And provides clear fallbacks to manual selection if discovery is inconclusive or a recommended integration fails And records an auditable log entry capturing selections, scopes, and timestamps upon confirmation
Smart Locale & Work Hours Seeding
"As a team admin, I want default work hours and holidays seeded from our organization’s regions so that scheduling is accurate from day one without manual setup."
Description

Infer primary locales, time zones, working hours, and public holiday calendars from domain signals and connected systems. Use tenant locale settings (Google Workspace/Microsoft 365), directory location attributes (Okta/Azure AD/Google Directory), and HRIS office/employee location distributions to compute region clusters and seed default work hours per region. Select applicable public holiday calendars per country and attach them to regions with a confidence score. For multi-region orgs, propose multiple region profiles and assign initial teams where signals exist. Present a review UI to accept or adjust hours, time zones, and holidays before applying to Timeglue’s org settings. This enables accurate, region-aware scheduling defaults from day one and reduces admin configuration overhead.

Acceptance Criteria
Onboarding: Domain-Based Tenant Locale Seeding
Given an admin enters and verifies a company domain linked to a Google Workspace or Microsoft 365 tenant When Timeglue fetches tenant locale and primary time zone settings Then the org default locale and primary time zone are preselected to match the tenant values And the selection records source=TenantSettings and confidence >= 0.80 And the detection completes within 4 seconds And if the tenant fetch fails or returns no values Then fallback heuristics (WHOIS/TLD) are used with confidence <= 0.60 and an audit log entry is created
Directory Location Clustering
Given Okta, Azure AD, or Google Directory is connected and at least 1,000 users have location attributes (country, city, or timeZone) When region clustering runs Then >= 90% of users are assigned to a region cluster and <= 10% remain in an "Unassigned" bucket And each cluster has an explicit time zone, country code, and a user count >= 5 or >= 5% of total users And the number of clusters is between 1 and 12 And the clustering job completes within 15 seconds for up to 50,000 users And each cluster stores a confidence score between 0 and 1
HRIS Merge and Signal Reconciliation
Given an HRIS integration is connected with office locations and employee work locations When merge and reconciliation across Tenant, Directory, and HRIS signals executes Then region coverage is >= 95% of active employees And the region distribution differs from the HRIS office distribution by <= 10% absolute for the top 3 regions And tie-breaking is deterministic given identical inputs And the reconciliation completes within 20 seconds for up to 50,000 employees And each region profile includes a confidence score and signal sources used
Public Holiday Calendar Attachment with Confidence
Given region profiles with resolved country codes When public holiday calendars are fetched Then the correct national holiday calendar is attached to each region with confidence >= 0.90 when the country is unambiguous And where multiple plausible calendars exist, the top 3 candidates are shown with descending confidence and a single default is selected And holidays for the current and next calendar year are loaded (>= 365 days forward) And no duplicate holidays are present per region And the calendar source and last sync timestamp are recorded
Default Work Hours Seeding per Region
Given region profiles with locale and time zone When default work hours are seeded Then each region is assigned working days and hours in local time using 30-minute increments And proposed hours pass validation (no overlaps, within 00:00–24:00, at least 1 hour/day if any day is enabled) And for regions with known alternate weekends (e.g., AE, SA), the default workweek is Sun–Thu And if no locale pattern is known, fallback defaults to Mon–Fri 09:00–17:00 local time with confidence <= 0.60 And the seeding completes within 2 seconds for up to 12 regions
Multi-Region Profiles and Initial Team Assignment
Given multiple regions are detected and directory groups or HRIS teams contain location keywords or codes When initial team assignments are proposed Then >= 80% of users are either mapped to a single region or explicitly marked Unassigned with a reason code And no user is assigned to more than one region And each proposed team mapping lists its source signal and confidence score And a CSV export of region-to-user assignments can be generated within 5 seconds for up to 50,000 rows
Review UI: Accept, Adjust, and Apply
Given region profiles, seeded hours, and holiday calendars are ready When the Review Settings UI is opened Then the UI lists each region with name, time zone, working days/hours, holiday calendar, confidence, and user count And admins can edit hours, time zone, and holiday calendar per region and see updated confidence And admins can Accept All or Apply selected regions And on Apply, changes persist to org settings atomically, a confirmation message is shown, and an audit event is recorded And manual overrides persist across future detections unless Reset to Suggested is chosen And the apply action completes within 2 seconds
Admin Consent & Least-Privilege Permission Scoping
"As a security-conscious admin, I want clearly guided consent with minimal permissions so that our compliance and risk requirements are met."
Description

Provide guided consent flows for each recommended integration with clearly documented data usage and minimal scopes (progressive consent where feasible). Support Google OAuth and Microsoft Graph admin consent for calendar and directory read access, plus Okta/Azure AD/Google Directory for user attributes needed by Timeglue. Store tokens securely with rotation, support consent renewal and revocation, and detect permission changes. Block risky over-scoped requests and present justifications for any escalations. This ensures security and compliance while enabling Domain Snap to establish necessary connections.

Acceptance Criteria
Google Workspace Admin Consent with Minimal Scopes
Given a verified Google Workspace super admin initiates Domain Snap setup When the Google integration consent step is displayed Then the consent screen requests only https://www.googleapis.com/auth/calendar.readonly, https://www.googleapis.com/auth/admin.directory.user.readonly, and (if group mapping is enabled) https://www.googleapis.com/auth/admin.directory.group.readonly And the screen displays per-scope data usage summaries and links to documentation And the admin can approve or decline When the admin approves Then a post-consent check successfully lists the admin’s primary calendar metadata and fetches one user record via Admin SDK Directory API And no write scopes are present in the granted token And tokens are stored encrypted at rest with keys managed by the platform KMS and secrets are redacted from logs
Microsoft Graph Admin Consent for Least-Privilege Calendar and Directory Read
Given a Microsoft 365 global admin selects the Microsoft integration during Domain Snap When the consent step renders Then requested application permissions are limited to Calendars.Read and User.ReadBasic.All (no write or Directory.Read.All) And the dialog displays per-permission data usage and justification When the admin consents Then the app can read a minimal test list of upcoming events from the admin’s calendar and fetch basic user attributes across the tenant And the granted permissions recorded in Timeglue match the requested set And tokens are stored encrypted and associated with the correct tenant ID
Progressive Consent Escalation with Justification
Given Timeglue attempts to read mailbox working hours to seed default schedules When the required permission (e.g., MailboxSettings.Read for Microsoft, or additional Directory scopes) is not yet granted Then the system pauses the operation and presents an escalation dialog showing the exact scope name, data fields to be accessed, purpose, and least-privilege rationale And the admin can choose Grant now, Remind me later, or Skip with reduced functionality When the admin selects Skip Then the feature proceeds with safe defaults (e.g., company-level hours) without accessing mailbox settings When the admin grants Then a post-consent validation confirms access only to the intended endpoint and updates the permissions ledger and audit log
Token Security, Rotation, and Revocation Handling
Given integration tokens are stored after consent Then access and refresh tokens are encrypted at rest with KMS-backed keys and rotated per provider guidance And refresh token rotation is honored and old refresh tokens are promptly invalidated When a token is revoked or scopes are reduced Then the system detects the condition within 10 minutes via webhook or error response, disables dependent jobs, surfaces an admin alert, and requests re-consent And upon revocation confirmation, all related tokens and cached credentials are purged within 5 minutes and the event is audit-logged with actor, time, provider, and reason
Okta/Azure AD/Google Directory Attribute Sync with Least Privilege
Given an admin connects a directory provider (Okta, Azure AD, or Google Directory) during Domain Snap Then only read-only permissions are requested for user attributes (Okta: okta.users.read; Azure AD: User.ReadBasic.All; Google: admin.directory.user.readonly) And the consent screen shows the exact attributes Timeglue will read (email, name, department, locale/time zone if available) When consent is granted Then a test sync retrieves up to 50 users and only the whitelisted attributes are processed and stored And attempts to access non-whitelisted attributes are blocked and logged And full sync runs only after the admin confirms the test results
Permission Drift Detection and Blocking Over-Scoped Requests
Given Timeglue prepares an API call to a connected provider When the call would require scopes beyond the tenant’s approved allowlist Then the request is blocked before transmission and the admin is prompted with justification and a re-consent option And a security event is recorded with requested scope, originating feature, and user context When a provider returns a token with broadened scopes due to external changes Then Timeglue restricts internal usage to the approved allowlist, flags the drift, and requires explicit admin confirmation to adopt any broader scopes
Manual Override, Review & Rollback
"As an admin, I want to review, edit, and roll back auto-configured settings so that I stay in control and can correct mistakes quickly."
Description

Offer a consolidated review screen where admins can accept or modify all Domain Snap suggestions, including chosen providers, scopes, region profiles, work hours, and holiday calendars. Allow manual selection when detection is ambiguous, and provide a one-click rollback to the pre-setup state. Maintain versioned configuration snapshots with diff history and the ability to re-run detection idempotently after changes (e.g., switching from Okta to Azure AD). This ensures admins retain control and can quickly correct or undo auto-configuration without risking data integrity.

Acceptance Criteria
Admin Reviews and Edits Domain Snap Suggestions
Given Domain Snap has produced suggested providers, scopes, region profiles, work hours, and holiday calendars And the user has Admin permissions When the admin opens the consolidated review screen Then all suggested values are displayed in editable fields with inline validation And each field can be individually accepted or modified before applying And dependent fields (e.g., scopes) revalidate when the provider is changed And clicking "Apply Configuration" persists the selections as a new Active configuration snapshot And the system returns a success response within 3 seconds under nominal load And the UI shows a confirmation within 2 seconds of successful apply
Manual Resolution of Ambiguous Provider Detection
Given detection yields multiple plausible providers or region profiles below the confidence threshold When the admin enters the review screen Then fields with ambiguity are marked with an "Ambiguous" state and explanatory tooltip And the system presents at least the top 3 detected candidates for each ambiguous field and a searchable picker And the "Apply Configuration" action remains disabled until all ambiguous selections are explicitly resolved And cancelling the review leaves the system unchanged with no partial configuration applied
One-Click Rollback to Pre-Setup State
Given at least one configuration snapshot has been applied When the admin clicks "Rollback to Pre-Setup" and confirms the action Then the system restores the application to the pre-setup state, removing active integrations, revoking tokens, and resetting work hours/holidays to defaults without deleting user or meeting data And a new snapshot is created labeled "Rollback" with a reference to the reverted-from version And any scheduled background jobs or webhooks from the rolled-back integrations are deactivated And the rollback completes within 30 seconds under nominal load And the UI displays a success status and the system is usable immediately after completion
Versioned Snapshots with Diff History
Given an existing Active configuration When any change is saved or applied from the review screen Then a new immutable configuration snapshot is created with version ID, timestamp, actor, source (Auto-Detect or Manual), and change summary And a diff view highlights field-level additions, removals, and modifications across providers, scopes, region profiles, work hours, and holiday calendars And the admin can list snapshots, view diffs, and restore any prior snapshot without data loss And restored snapshots become the new Active configuration and generate their own snapshot entry with a link to the source version
Idempotent Re-Run of Detection After Manual Changes
Given the current configuration contains manual overrides When the admin clicks "Re-run Detection" Then the system generates a proposed configuration without auto-applying changes And fields previously overridden are marked as Locked and are not changed in the proposal And the merge preview displays only the proposed changes and their impact And applying the proposal creates a new snapshot and leaves locked fields intact And repeating detection without intervening changes yields the same proposal (idempotent behavior)
Switching Identity Provider from Okta to Azure AD
Given Okta is the current configured identity provider with active tokens and webhooks When the admin selects Azure AD in the review screen, completes auth, and applies the configuration Then Okta integration is cleanly deauthorized (tokens revoked, webhooks unsubscribed) and removed from Active configuration And Azure AD integration passes a health check within 10 seconds and becomes the Active provider And no duplicate user directory records, calendars, or sync jobs are left behind And a snapshot diff records the provider switch and associated scope changes
Concurrency Control and Audit Logging for Configuration Changes
Given two admins are viewing the review screen for the same configuration When Admin A applies changes creating a new snapshot Then Admin B is prevented from applying stale changes and sees a concurrency conflict message prompting refresh And all apply, rollback, detection, and restore actions are logged with timestamp, actor, outcome, and snapshot IDs And audit logs are retained for at least 180 days and can be exported by admins
Audit Logging, Privacy & Compliance Controls
"As a compliance officer, I want complete auditability of detections, consents, and configuration changes so that we can satisfy regulatory and security review requirements."
Description

Capture detailed, immutable audit logs for all detections, recommendations, consents, applied configurations, and rollbacks, including actor, timestamp, source signals, and confidence levels. Mask PII where not essential, support export to SIEM, and honor data retention policies. Provide data flow diagrams and consent records to assist with SOC 2, ISO 27001, and GDPR readiness. Expose transparency reports in-app so admins can trace why a given integration or locale was selected. This builds trust and supports enterprise compliance requirements tied to automated setup.

Acceptance Criteria
Immutable Audit Trail for Auto-Detection and Setup
Given Domain Snap performs a domain detection or applies a recommended configuration When an event of type detection, recommendation, consent, apply, or rollback occurs Then an audit log entry is appended with fields: event_id (UUIDv4), event_type, actor_id (or "system"), actor_role, timestamp (UTC ISO-8601), source_signals[], confidence_score (0.0–1.0), target_resource, org_id, request_id, result_status And the entry is stored in write-once (WORM) storage and cryptographically chained via SHA-256 prev_hash to the prior entry And any attempt to modify or delete an existing entry is rejected and a tamper_attempt event is recorded
PII Masking and Least-Privilege Viewing
Given an audit viewer without PII privilege When they view or export audit logs Then user-identifiers (emails, employee IDs, IPs) are masked (email local-part hashed with domain preserved; IDs tokenized) and free-text fields are redacted And only fields flagged essential_for_security are shown in clear Given a viewer with PII privilege and a recorded justification When they view or export logs Then PII is visible and a pii_access event is logged with viewer_id, timestamp, justification Given SIEM export runs without the include_pii flag Then exported records are masked identically to in-app views
SIEM Export and Delivery Guarantees
Given an org admin configures a SIEM destination via HTTPS webhook or syslog TCP/TLS When export is enabled with event-type and date-range filters Then events are sent as JSON Lines conforming to schema version v1 with X-Signature (HMAC-SHA256) over payload And events are delivered within 60 seconds of creation at p95 and retried with exponential backoff up to 24 hours on 5xx/timeouts And delivery is exactly-once per destination using idempotency key = event_id Given the destination is unreachable for >5 minutes Then an alert is sent to org admins and export health shows Degraded with last_success_at timestamp
Retention, Purge, Anonymization, and Legal Hold
Given an org retention policy is configured per event category (e.g., detection 90d, consent 7y) When the retention period elapses Then affected events are purged or irreversibly anonymized within 24 hours by a scheduled job And a purge_summary record is created with counts, categories, and time window Given a legal hold is applied to an org, user, or event category Then matching events are excluded from purge until the hold is removed, and hold_applied/hold_removed audit events are recorded Given a DSAR delete request for a user When processing the request Then user-identifiers in audit events are pseudonymized within 30 days unless under legal hold, while non-PII metadata is retained
In-App Transparency Report for Selection Rationale
Given an admin opens the transparency report for a Domain Snap session When investigating why specific integrations and locale were selected Then the report displays the decision graph with source signals, rules fired, weights, confidence scores, timestamps, and contributing evidence And linked consent records and configuration diffs are visible And the view loads within 2 seconds at p95 for up to 1,000 related events And the report can be exported to PDF and JSON containing the same fields
Consent Records and Revocation Handling
Given a user or admin consents to connect an integration or apply seeded work hours/holidays When consent is submitted Then a consent record is stored with: consent_id, actor_id, timestamp (UTC), consent_text_version, scope, locale, IP, user_agent, and links to affected resources And a consent_captured audit event is appended Given consent is revoked When revocation is submitted Then the related integration/config is rolled back within 5 minutes, downstream tokens are invalidated, and a rollback audit event is appended with result_status And future automated recommendations for that scope require fresh consent
Data Flow Diagram Generation and Evidence Export
Given the org has at least one integration configured When viewing Compliance > Data Flows Then a versioned data flow diagram renders showing systems, processors, data categories, transfer paths, and storage regions And the diagram updates within 10 minutes of any configuration change And it can be exported as SVG and JSON (including version, generated_at, checksum) And an evidence bundle for SOC 2/ISO 27001/GDPR can be downloaded containing the DFD, audit log schema, sample audit entries, retention policy, and consent record references

Quick Connect

One-click, read-only onboarding to popular HRIS/IdP systems imports teams, managers, locations, and work‑hour policies. You get accurate rosters and sane defaults in minutes—no CSVs, no mapping marathons.

Requirements

One-click OAuth Connectors
"As a workspace admin, I want to connect our HRIS/IdP with one click so that I can import our roster and org structure without manual files or field mapping."
Description

Provide turnkey, one-click OAuth integrations with leading HRIS and IdP providers (e.g., Okta, Azure AD, Google Workspace, BambooHR, Rippling, Workday, HiBob) to initiate a read-only import of users, groups/teams, manager relationships, locations, and applicable policy attributes. The connector framework must standardize auth flow, scope requests, token handling, and error states, enabling a consistent onboarding experience that completes in minutes without CSVs. This integrates with Timeglue’s onboarding wizard and sync pipeline, exposing provider availability, prerequisite checks, and post-connection health status within the admin UI. Expected outcome: reliable, low-friction setup that scales across providers and reduces admin time-to-value.

Acceptance Criteria
Complete one-click OAuth connect flow for supported providers
- Given an admin has selected a supported provider in the onboarding wizard, When they click "Connect", Then they are redirected to the provider OAuth consent screen within 2 seconds. - Given the consent screen is shown, When the admin grants consent, Then Timeglue completes the token exchange and returns to the wizard within 10 seconds. - Given a successful token exchange, Then the connector status displays "Connected" in the UI within 3 seconds. - Then no CSV upload or manual field-mapping steps are presented during the connect flow. - Then an audit event "connector_connected" is recorded with provider, tenant/domain, user, and timestamp.
Enforce read-only, least-privilege scopes per provider
- For each provider, the requested OAuth scopes match a pre-approved, read-only scope set stored in configuration. - No write/admin scopes are requested; automated test asserts requested scopes ⊆ approved list. - If the provider returns broader scopes than approved, the connection is aborted, no tokens are persisted, and the UI displays "Excessive scopes requested" with remediation. - The consent screen displays only the minimal read-only scopes; snapshot of requested scopes is stored with the connection record. - Access/refresh tokens are never displayed or logged; scope strings are logged as metadata only.
Kickoff and complete initial roster import after connect
- Given a connection transitions to "Connected", When the wizard advances, Then an initial import job is enqueued within 10 seconds. - The import retrieves users, groups/teams, manager relationships, locations, and work-hour policy attributes available to the granted scopes. - The import completes within 3 minutes for 1,000 users or within 15 minutes for 10,000 users under normal provider rate limits. - Mapping: required fields are populated (user id, email, display name, group memberships, manager id where present); optional fields fall back to sane defaults. - Idempotency: re-running the import produces no duplicate users or groups; primary keys are provider-tenant-scoped. - Data quality: ≥99.5% of non-empty provider records are successfully ingested; failed records are quarantined with error codes and counts visible in UI.
Surface prerequisites and provider availability in admin UI
- The provider card shows prerequisite checklist (e.g., required admin role, marketplace app install, allowed redirect URIs) with links to docs. - The Connect button remains disabled until all prerequisites are marked Complete. - Provider availability is fetched at least every 60 seconds; if status ≠ Operational, the Connect action is disabled and a banner indicates outage/degradation. - Copy-to-clipboard for app IDs and redirect URIs works and is confirmed via toast. - All prerequisite state changes and availability status are written to the audit log.
Standardize error handling and retry across connectors
- Error taxonomy is unified across providers: CANCELLED, DENIED, TIMEOUT, INVALID_SCOPE, MISSING_PERMISSIONS, PROVIDER_UNAVAILABLE, TOKEN_EXCHANGE_FAILED, RATE_LIMIT, DATA_MAPPING_ERROR. - Transient errors (TIMEOUT, PROVIDER_UNAVAILABLE, RATE_LIMIT) are retried up to 3 times with exponential backoff, total retry window ≤ 90 seconds. - Each user-visible error shows a friendly message, provider error code, and a Retry action; retries are idempotent. - Metrics emitted per error (count, provider, error_code); alerting threshold is configurable (e.g., 5% failure rate over 15 minutes triggers alert). - Success and failure paths return the user to a consistent wizard state without dead-ends.
Secure token storage, refresh, revocation, and auditability
- Access and refresh tokens are stored encrypted with KMS-backed keys; only the sync service role can access them. - Tokens are refreshed automatically before expiry with jitter; failed refresh marks connection as "Needs Reconnect" and notifies admins via email within 5 minutes. - User-initiated Disconnect revokes tokens at the provider (where supported) and purges stored credentials within 60 seconds; audit event "connector_disconnected" recorded. - No secrets or tokens appear in logs or UI; security lint and e2e tests verify redaction. - Token metadata (provider, scopes, created_at, expires_at) is retained for audit without exposing token values.
Expose post-connection health status and sync controls
- Admin UI shows connector card with Provider, Tenant/Domain, Status (Healthy, Degraded, Needs Reconnect), Last Sync Time, Last Error, counts (# users, # groups), and actions (View Logs, Disconnect, Force Sync). - Health checks run every 15 minutes; if no successful sync occurs for >1 hour, status becomes Degraded; if token refresh fails or scopes invalid, status becomes Needs Reconnect. - View Logs opens a paginated list of the last 100 sync events with filters by level, date, and component; PII is redacted by default. - Force Sync enqueues a sync within 30 seconds; upon completion, Last Sync Time updates and a success toast is shown. - Health status and action events are captured as structured logs and metrics.
Read-only Permissions & Consent
"As a security-conscious admin, I want explicit read-only permissions and transparent consent so that I can approve connections without risking unnecessary data exposure."
Description

Enforce least-privilege, read-only access across all connectors with clearly enumerated scopes and human-readable consent screens. Tokens must be encrypted at rest, rotated on schedule, and revocable from Timeglue. Display requested attributes by provider and explain how each is used (e.g., team assignment, location timezone, work-hour policy). Provide a permissions diffs view on scope changes and block connection if elevated scopes are required. This safeguards privacy, builds trust, and supports compliance needs while still delivering accurate imports.

Acceptance Criteria
Consent Screen Shows Provider Scopes and Attribute Usage
Given an admin initiates Quick Connect to a supported provider When the consent screen is displayed Then the provider name, requested OAuth scopes, and requested attributes are listed in human-readable language And each attribute includes a plain-language purpose (team assignment, location timezone, work-hour policy) And the primary action remains disabled until the admin explicitly consents And consent capture stores admin ID, timestamp, provider, scopes, attributes, and consent text version And the consent text meets a Flesch-Kincaid grade level of 9 or lower And the consent screen meets WCAG 2.1 AA for contrast, keyboard navigation, and focus indicators
Enforce Read-Only Least-Privilege Scopes
Given Timeglue maintains a per-provider allowlist of read-only scopes When requesting authorization for a new connection Then only allowlisted read-only scopes are requested from the provider And any attempt to include write/admin scopes blocks the flow with an explanation of least-privilege policy And post-connection, all outbound API operations are limited to read-only endpoints (no create/update/delete) And automated tests assert scope requests and API method usage for each supported provider on every release
Permissions Diff Before Scope Changes and Reconnect
Given an existing connector with previously granted scopes When Timeglue requires additional or different scopes for that provider Then a permissions diff view highlights added, removed, and changed scopes with clear descriptions And the connection is blocked until an admin reviews and approves the new scopes in-app And the approval flow records approver ID, timestamp, old vs. new scopes, and rationale in the audit log And an email notification is sent to connector owners with a link to the diff view And if any added scope is elevated (write/admin), the change is rejected and cannot be approved
Token Encryption at Rest and Scheduled Rotation
Given a connector token is issued and stored by Timeglue When the token is persisted Then it is encrypted at rest using AES-256-GCM with keys managed by a FIPS 140-2 validated KMS And access to decrypt keys is restricted to the connector service role with least-privilege And a rotation job runs daily and rotates tokens every 90 days or before provider TTL expiry, whichever is sooner And admins can trigger on-demand rotation from the UI And failed rotation disables syncs for that connector and alerts admins via email and webhook And all token lifecycle events (create, rotate, revoke, delete) are written to an immutable audit log
Admin Revokes Connector Tokens from Timeglue
Given an admin opens the connector detail page When the admin clicks "Revoke Access" and confirms Then the local stored token is immediately destroyed And a provider-side revocation endpoint is called when available and the response is recorded And subsequent sync attempts return a "Revoked" connector status and do not make data requests And the UI reflects the disconnected state within 5 seconds of confirmation And an audit log entry records admin ID, timestamp, connector ID, and revocation result
Attribute Transparency and Data Minimization on Consent
Given a provider is selected during Quick Connect When the attribute list is viewed on the consent screen Then only attributes required to import teams, managers, locations/timezones, and work-hour policies are requested by default And optional attributes are clearly labeled as optional and are off by default And each attribute displays its field name/path and specific usage within Timeglue And a link to the provider’s scope/permissions documentation is provided And the admin can download a JSON summary of the scopes, attributes, and purposes prior to connecting
Import Restricted to Consented Attributes Only
Given a connector is authorized with a recorded set of scopes and attributes When a sync job runs Then only endpoints within the granted scopes are called And only attributes listed in the consent record are stored; extraneous fields are discarded before persistence And schema validation rejects insertion of fields not present in the consent record And policy enforcement blocks any attempt to call endpoints outside read-only scope And integration tests verify that no write operations are performed against provider APIs during sync
Directory Normalization & Mapping
"As an implementation owner, I want imported directory data normalized to Timeglue’s structure so that teams and relationships work correctly without manual cleanup."
Description

Normalize imported directory data into Timeglue’s domain model: users, teams, manager-of, location/timezone, and policy-related attributes. Handle varied provider schemas, locales, and attribute names with a mapping layer that applies smart defaults and supports optional custom field mapping. Deduplicate users across sources, resolve primary email/login, and preserve external IDs for referential integrity. Persist mapping rules for subsequent syncs and expose a preview to verify transformations. This ensures clean, consistent data powering scheduling logic and smart link defaults.

Acceptance Criteria
Smart Default Mapping (No Custom Rules)
Given a connected directory with no custom field mappings When the initial sync runs Then all records with both email and full name are created as Users with those fields set And default alias rules map department→Team, manager→ManagerOf, office/location→Location, timeZone→Timezone, workingHours/holidayCalendar→Policy where present And records missing email or full name are skipped, with per-record reasons captured in the sync report And the sync report summarizes totals for processed, created, updated, merged, and skipped by reason
Custom Field Mapping Override
Given an admin defines custom mapping rules that override defaults (e.g., userPrincipalName→email, givenName+sn→fullName, siteCode→Location) When a sync runs Then the overridden mappings are applied to 100% of processed records And default mappings are used only for fields not overridden And invalid or unsupported mapping targets are prevented at save time with a validation error and no changes applied
User Deduplication Across Sources
Given Source A and Source B are connected and contain overlapping users matched by normalized email or externalId When a sync runs Then exactly one User exists per matched person after sync And User.primarySource is set per configured source precedence, with secondary identities stored in linkedIdentities (source, externalId, email) And conflicting attribute values are resolved per precedence rules and listed in the sync report And the report includes counts of merged users and details of any unresolved conflicts
Primary Email/Login Resolution
Given a user has multiple email identifiers across sources and source precedence rules are configured When resolving the primary login during sync Then primaryLogin is set to the highest-precedence verified email, else the highest-precedence available email And primaryLogin is unique across all Users And any collisions are reported with affected externalIds and are not updated until manually resolved
Persisted Mapping Rules for Subsequent Syncs
Given mapping rules are saved at time T1 When a subsequent sync runs with unchanged source inputs Then the same inputs produce identical outputs (idempotent) and the applied ruleset version (timestamp, author, checksum) is recorded And when a provider renames an attribute to a known alias, the alias registry auto-maps and logs a deprecation warning without blocking the sync And when a mapped required attribute is removed with no alias, the sync fails fast with a "Missing required field mapping" error and no partial writes occur
Transformation Preview and Approval Gate
Given an admin opens Preview before applying mappings When the preview is generated Then it displays a sample of at least 50 records (or full set if <50) with source→target values, dedupe/merge indicators, and unresolved conflicts And it shows rollups: to-create, to-update, to-merge, to-skip by reason, and blocking errors And no changes are persisted until the admin clicks Apply And when Apply is clicked, the previewed ruleset (checksum) is persisted and the sync starts using that exact ruleset
Location and Timezone Normalization
Given imported records include location and timezone values in varied formats and locales When mapping runs Then location strings are normalized to Location objects (country, region/state, city when derivable) and stored consistently And timezone values are converted to valid IANA IDs; common aliases (e.g., PST, Eastern Standard Time, Etc/GMT+0) are resolved; invalid/ambiguous values are flagged and reported And when timezone is missing, the system defaults to a location-derived timezone if unique, else the org default policy; all fallbacks are logged
Work Hours & Holiday Policy Inference
"As a team lead, I want default working hours and holidays inferred from our directory so that meeting windows are sane without hand-configuring every team."
Description

Derive default working hours, timezones, and holiday calendars from imported location and policy attributes, enriching with region-specific public holidays. Where explicit schedules are missing, apply configurable heuristics by location and role (e.g., standard 9–5 local, lunch buffers) and surface exceptions for part-time or shift workers. Generate organization-, team-, and location-level defaults that Timeglue uses to pre-paint acceptable meeting windows and prevent after-hours invites. Admins can override inferred policies and set precedence rules between company, team, and individual levels.

Acceptance Criteria
Location-Based Default Work Hours Inference
Given a user record imported with a valid city and country and no explicit working hours And organization-level heuristics define "Standard 09:00–17:00 local, Monday–Friday, 60-min lunch at 12:00" When Quick Connect completes the initial sync Then the user's timezone is set to the IANA zone matching the location And the user's default working hours are set to 09:00–17:00 in their local timezone on Monday–Friday And the 12:00–13:00 lunch buffer is excluded from acceptable meeting windows And the inferred defaults are visible on the user's policy summary within 10 seconds of sync completion
Region-Specific Public Holiday Enrichment
Given location defaults exist for at least two countries with published public holiday datasets When the policy inference runs Then the current and next calendar year's public holidays for each country are attached to the corresponding location default And all attached public holidays are marked as non-working days that prevent after-hours invites And holiday data is refreshed at least daily and updates are reflected within 24 hours of source change And an audit entry records the holiday source, version/date, and number of holidays applied per location
Role- and Location-Based Heuristic Fallbacks
Given users without explicit schedules and with roles "Engineering" or "Support" and valid locations And admin-configured heuristics exist: Engineering = 10:00–18:00 Mon–Fri with 60-min lunch; Support = 08:00–16:00 Mon–Sun with 30-min lunch When inference runs Then each user's default schedule matches the heuristic for their role and location timezone And Support users receive Mon–Sun windows; Engineering users receive Mon–Fri windows And the configured lunch buffer is excluded from meeting windows And if a role has no heuristic, the location default "Standard 09:00–17:00" is applied
Exception Surfacing for Part-Time and Shift Workers
Given a user imported with HRIS attributes indicating FTE < 1.0 or a shift code When inference runs Then the user is flagged as an exception in the "Policy Exceptions" list with reason "Part-time/Shift" And no pre-painted meeting windows are created outside the HRIS-provided shift times And admins must confirm or edit the schedule before invites outside HRIS shift times are allowed And an email and in-app notification are sent to organization admins summarizing new exceptions within 15 minutes
Precedence Rules Between Company, Team, and Individual
Given company-level defaults exist, team-level overrides for a specific team, and an individual override for a user in that team And the precedence is set to Individual > Team > Company When computing the final schedule for that user Then the individual override is applied and supersedes team and company defaults And the effective schedule includes all applicable non-working days from the user's location holiday calendar And the applied precedence order and sources are displayed in the policy debugger And when the precedence is changed to Team > Individual > Company, recomputation reflects the team override within 10 seconds
Pre-Painted Windows and After-Hours Invite Prevention
Given inferred or overridden work hours and holiday calendars for a user When an organizer opens the scheduling timeline for that user Then acceptable meeting windows are pre-painted only within working hours on working days, excluding lunch buffers and public holidays And attempts to schedule outside the pre-painted windows are blocked with error code TG-INV-001 and the message "Outside recipient work hours" And invites within pre-painted windows succeed with status "Scheduled" And these rules apply consistently across organizers and timezones
Admin Override, Propagation, and Audit
Given an admin edits team-level default hours from 09:00–17:00 to 10:00–18:00 local When the change is saved Then affected users' effective schedules are recalculated and visible in the UI within 2 minutes And all future pre-painted windows are updated accordingly without duplicating existing events And an immutable audit log entry is created capturing editor, timestamp (UTC), scope (Team), old/new values, and affected user count And a rollback action is available for 24 hours to restore the previous policy
Incremental Sync & Webhook Refresh
"As an admin, I want automatic incremental updates and webhooks so that Timeglue stays aligned with our source of truth without manual intervention."
Description

Support both scheduled incremental syncs and webhook-based refreshes to keep rosters, relationships, and policies current with minimal load. Implement delta cursors, pagination, rate limiting, and idempotent upserts to handle large directories. Expose sync cadence controls, last-sync timestamp, change counts, and a manual re-sync action in the admin UI. Automatically reconcile deprovisioned users and org changes to maintain accurate scheduling defaults and prevent invitations to inactive accounts.

Acceptance Criteria
Scheduled Incremental Sync via Delta Cursors
Given a last successful sync cursor T and 100k directory objects changed since T When the scheduled sync runs at the configured cadence Then only changes since T are requested using the vendor delta mechanism And all pages are fetched until no next page token is returned And vendor rate limits are not exceeded; on 429, exponential backoff with Retry-After is honored And the run completes within the job timeout (default 15 minutes) for up to 100k changed objects Given duplicate records for the same HRIS ID appear across pages or runs When upserting into Timeglue Then the operation is idempotent, creating no duplicates and atomically updating existing records Given the sync completes When the run finishes Then the last-sync timestamp is set to the highest modified_at processed And change counts (created, updated, deactivated, unchanged, errored) are recorded and exposed
Webhook-Triggered Refresh and Exactly-Once Processing
Given a webhook event with unique id E for user U is delivered 3 times When the webhook processor handles these deliveries Then the update for U is applied exactly once and subsequent deliveries are acknowledged without reapplying Given a webhook event for a known object arrives When processed Then the affected resource is refreshed and visible in the admin UI within 60 seconds Given a webhook event references a resource older than the current cursor checkpoint When processed Then the system fetches the latest state from the source and upserts idempotently so stored data remains current
Admin UI: Sync Cadence, Telemetry, and Manual Re-sync
Given an admin opens Quick Connect → Sync Settings When viewing the settings Then a cadence control is available and accepts values between 5 minutes and 24 hours with validation And the last-sync timestamp is displayed in the organization’s timezone And the last run’s change counts per entity type are displayed Given no sync is currently running When the admin clicks Re-sync Now Then a new incremental sync is queued within 10 seconds and progress status is visible until completion Given a sync is in progress When viewing the Re-sync Now control Then the control is disabled with an explanatory tooltip and progress state is shown
Deprovisioned User Reconciliation
Given a user is marked inactive or deleted in the HRIS/IdP When the next incremental sync or webhook runs Then the user is marked inactive in Timeglue within 5 minutes And removed from active rosters and manager relationships while preserving historical data And subsequent runs do not recreate the user (idempotent deactivation)
Propagation of Manager, Location, and Work-Hour Policy Changes
Given a user’s manager changes from A to B in the source system When the next sync completes Then the user’s manager in Timeglue reflects B and dependent scheduling defaults update within 10 minutes Given a user’s work location/time zone changes When the next sync completes Then the user’s time zone is updated and future scheduling windows recalculate using the new time zone Given a user’s work-hour policy or holiday calendar changes When the next sync completes Then the new policy is applied and scheduling defaults reflect the change before the next booking attempt
Robust Error Handling, Retry, and Checkpointing
Given the vendor API returns 429 rate limit errors during a run When the sync executes Then exponential backoff is applied (respecting Retry-After) up to 5 retries per request without exceeding rate limits And if retries are exhausted, the run is marked failed with an error message visible in the admin UI And the next run resumes from the last successful cursor without data loss Given a network timeout occurs on page N of a paginated delta When the sync resumes Then processing restarts from page N using the persisted cursor and previously applied pages are not reprocessed Given an unexpected 5xx error occurs When the run completes Then failure metrics and logs with correlation IDs are emitted and are viewable in monitoring, and an alert is raised
Import Validation & Conflict Resolution
"As an admin, I want validation and clear conflict resolution so that I can fix issues quickly without blocking the entire import."
Description

Validate incoming data for completeness and logical consistency (e.g., missing timezone, cyclic manager chains, overlapping or invalid work-hour windows). Provide clear, actionable errors with suggested fixes, quarantine problematic records, and continue importing clean data. Offer conflict policies (prefer HRIS vs. IdP, most recent update, or admin override) and surface a resolution queue in the UI. This prevents bad data from degrading scheduling accuracy while keeping onboarding fast.

Acceptance Criteria
Required Fields Validation and Quarantine
Given a Quick Connect import containing both valid and invalid employee records When server-side validation executes Then records missing required fields (externalId, primaryEmail, timezone, locationId) are flagged with error codes MISSING_FIELD.<FIELD> and moved to the quarantine queue And records with all required fields pass validation and are imported And quarantined records do not modify existing roster data And the import summary reports counts: processed, imported, quarantined, conflicts, skipped, and processed = imported + quarantined + skipped
Cyclic Manager Hierarchy Detection
Given an incoming dataset where any employee’s manager chain forms a cycle When hierarchy validation runs Then every record participating in the cycle is quarantined with error MANAGER_CYCLE_DETECTED including the detected path (e.g., A→B→C→A) And non-cyclic records import successfully And suggested fix includes setting one managerId to null or to a valid root
Work-Hour Policy Validity and Overlap Rules
Given incoming workHours policies per user and location When validating time windows Then each window must have start < end within a 24h day, use a valid timezone, and not overlap with other windows for the same day And windows outside 00:00–24:00 or overlapping are rejected with error INVALID_WORK_HOURS and quarantined at the policy level And the affected user is still imported with the organization's default work-hours policy applied And holidays listed in the connected source result in zero work windows unless explicitly marked as exceptions
Conflict Policy Application Across Sources
Given conflicting attribute values for the same user from HRIS and IdP And an admin-selected conflict policy is active (Prefer HRIS, Prefer IdP, Most Recent Update) When import runs Then the chosen policy determines the winner per conflicting field and the losing value is recorded as discarded with source and timestamp And an audit entry is created capturing field, sources, values, policy, winner, actor, and time And non-conflicting fields are merged without change And the import summary reports the number of conflicts resolved by policy
Actionable Error Messages
Given any validation failure during import When errors are surfaced Then each error includes fields: code, field, recordId, sourceSystem, severity, message, suggestion, docsUrl And messages are human-readable in English and map 1:1 to error codes And errors are visible in the Resolution Queue and downloadable as CSV and JSON
Resolution Queue UI and Admin Overrides
Given there are quarantined records and conflicts When an admin opens the Resolution Queue Then items are grouped by type (Conflicts, Missing Fields, Invalid Work Hours, Manager Cycles) with counts And filters are available by type, sourceSystem, field, assignee, and status (Open, Resolved, Snoozed) And each item shows recordId, field, incoming values by source, policy decision, suggested fix, and actions And actions include: accept policy decision, apply admin override value, edit missing/invalid fields, bulk select and revalidate And approved changes are applied to the roster and visible in People > Team within 30 seconds
Targeted Re-validation and Safe Reprocessing
Given one or more quarantined items have been corrected via override or source updates When the admin triggers Revalidate on selected items Then only those items are reprocessed without re-importing unaffected records And duplicates are not created; idempotency key prevents repeated application of the same change And successful items move to Resolved, failures remain Open with updated error messages And an audit log records the revalidation run with counts and actor
Admin Review & Bulk Actions
"As an onboarding admin, I want a preview and bulk-edit step so that I can adjust mappings and policies before finalizing the import."
Description

Deliver a guided review step showing import summaries (user counts, teams, locations, inferred policies) with the ability to include/exclude org units, remap teams, set default timezones, and apply bulk overrides before committing. Provide a dry-run preview with diffs and an undo/rollback option after apply. This minimizes surprises, reduces rework, and ensures imported data reflects operational reality from day one.

Acceptance Criteria
Import Summary Display and Accuracy
Given a completed HRIS/IdP data fetch When the Admin Review step loads Then the summary displays total counts for users, teams, locations, and inferred work-hour policies And the counts exactly match the fetched dataset And the summary shows the source system name, connector scope, and last sync timestamp And the summary renders in ≤2 seconds for datasets up to 10,000 users
Include/Exclude Org Units During Review
Given the org tree is displayed in the review step When an admin marks an org unit as Excluded Then all its subordinate units and users are marked Excluded and preview counts decrement accordingly And when the unit is marked Included again, counts update to reflect restoration And excluded units/users do not appear in the dry-run diff as pending changes And include/exclude selections persist across page reloads and sessions for the same admin for at least 24 hours or until Apply or Cancel
Team Remapping Before Import
Given imported teams that do not match existing Timeglue teams When an admin remaps imported Team X to existing Team Y Then the dry-run preview shows users reassigned to Team Y and Team X is not created And multiple imported teams can be merged into one existing team via remap And name collisions or circular mappings are prevented and surfaced with actionable warnings And the mapping applies consistently to all affected users in preview and on apply
Default Timezone Assignment
Given a default timezone is selected in the review step When users lack an explicit timezone from the source system Then those users are assigned the selected default timezone in the preview And users with explicit timezones retain their values unchanged And the preview displays the count of users impacted by the default assignment And only valid IANA timezone identifiers can be selected
Bulk Policy Overrides
Given an admin selects one or more org units or users When a bulk override is applied for work-hour policy and/or holiday calendar Then the preview displays the override on each affected entity and shows the total affected user count And overrides cascade to child units unless explicitly overridden at a lower level And an admin can revert an override before Apply, restoring inherited values And conflicting overrides trigger a resolution prompt before proceeding
Dry-Run Preview with Diffs
Given review selections and mappings are configured When the admin opens the dry-run preview Then the diff displays sections for Added, Updated, Unchanged, and Deactivated And per user, changed fields show before/after values for team, location, timezone, and policy And exporting the diff as CSV is available and matches on-screen counts And no production data is mutated during dry-run And the preview generates in ≤5 seconds for up to 10,000 users
Apply and Undo/Rollback
Given a dry-run preview has no blocking errors When the admin clicks Apply Then the system commits the changes and shows a success summary with counts by change type And an Undo option remains available for 30 minutes to revert to the pre-apply state And rollback restores all affected entities and logs an auditable event linking apply and undo And on partial failure the operation is transactionally rolled back and an error report with retry option is presented

Locale Mapper

Automatically assigns each person’s time zone and holiday set using HRIS location, office assignment, and calendar locale. Flags anomalies for quick review, eliminating manual timezone math and misclassified holidays.

Requirements

HRIS Data Sync Connector
"As a workspace admin, I want Timeglue to automatically pull location and office data from our HRIS so that team members are assigned accurate time zones without manual entry."
Description

Integrate with leading HRIS providers to ingest user location, office assignment, employment status, and email identifiers, normalizing records into Timeglue profiles. Support OAuth and API key auth, scheduled incremental syncs and webhooks for near‑real‑time updates, idempotent upserts, and robust retry/backoff. Map users by verified email with safeguards for duplicates and contractors. Validate and sanitize incoming fields, and expose sync health metrics and error reporting. The resulting location/office attributes become primary inputs to Locale Mapper’s time zone and holiday assignment pipeline, feeding Timeglue’s scheduling engine.

Acceptance Criteria
OAuth and API Key Authentication Support
- Given a supported HRIS with OAuth 2.0, When an admin completes the OAuth flow with valid client credentials and consent, Then Timeglue stores encrypted access/refresh tokens and a connectivity test returns HTTP 200. - Given an access token has expired, When the connector attempts a refresh, Then a new access token is obtained using the refresh token and the subsequent API call succeeds with HTTP 200 without user interaction. - Given API key authentication is configured, When a valid API key is saved, Then a connectivity test returns HTTP 200; When an invalid key is entered, Then the test returns HTTP 401 and the secret is not persisted. - Given credentials are saved, When the service restarts, Then secrets remain encrypted at rest and are not logged in plaintext.
Scheduled Incremental Syncs and Backfill
- Given initial setup, When the first scheduled sync runs, Then all active employees are fetched, normalized, and upserted into Timeglue profiles and a checkpoint (e.g., last_updated_at) is stored. - Given a configured sync interval of F minutes, When subsequent syncs run, Then only records changed since the checkpoint are requested and processed and the checkpoint advances to the latest processed cursor. - Given a service outage lasting Z minutes, When the connector resumes, Then it continues from the last successful checkpoint and no HRIS changes within the outage window are missed or duplicated. - Given a manual backfill is requested for a date range, When executed, Then pagination completes, rate limits are honored, and all records in range are upserted without affecting the incremental cursor.
Webhook Near-Real-Time Updates
- Given the HRIS sends a user.updated webhook, When a payload with a valid signature is received, Then the event is acknowledged with HTTP 2xx, enqueued within 1 second, and processed to upsert the profile within 60 seconds. - Given a webhook with an invalid/missing signature is received, When verification runs, Then the request is rejected with HTTP 401 and no processing occurs. - Given transient processing errors occur, When retries are attempted, Then exponential backoff is applied with jitter up to N attempts; on final failure the event is moved to a dead-letter queue with error details recorded. - Given duplicate webhook deliveries for the same change, When processed, Then the resulting upsert is idempotent and produces a single consistent profile version.
Idempotent Upserts and Duplicate Handling
- Given a profile exists for email E, When an HRIS record for E is processed, Then an upsert updates only changed fields and does not create a second profile. - Given the same HRIS record is delivered multiple times (batch or webhook), When processed, Then only one write occurs as evidenced by stable profile ID and version/timestamp semantics. - Given two HRIS persons share the same email E, When encountered, Then both source IDs are linked to an anomaly record, auto-merge is blocked, and an operator review flag is created. - Given conflicting concurrent updates arrive, When resolved, Then last-write-wins or configured resolution policy is applied deterministically and logged.
Email Mapping and Contractor Safeguards
- Given a configured set of verified corporate domains, When a person’s primary email matches a verified domain and is marked verified in HRIS, Then the connector maps and links the HRIS person to the Timeglue profile by that email. - Given employment_status = contractor/vendor/temporary, When processing, Then the person is excluded from auto-provisioning by default unless allow_contractors is enabled, and this decision is logged. - Given a person has secondary emails, When mapping, Then the corporate primary is preferred; if none exists, Then the record is flagged for review and not auto-provisioned. - Given an email is missing or malformed, When validation runs, Then the record is skipped for provisioning and an error with the offending field is emitted.
Field Validation and Sanitization
- Given incoming location fields, When normalized, Then country codes conform to ISO 3166-1 alpha-2 and region/state to ISO/standardized lists; unrecognized values are left blank and flagged without failing the batch. - Given office_assignment strings, When matched, Then they are trimmed, lowercased, mapped to a known office slug, or flagged unknown with a remediation hint. - Given employment_status values, When validated, Then only members of the allowed set {active, terminated, leave, contractor, vendor} are accepted; others default to unknown and trigger a validation error event. - Given arbitrary string inputs, When sanitization runs, Then leading/trailing whitespace and control characters are removed, HTML/script content is stripped, and invalid UTF-8 sequences are replaced. - Given a record fails any validation, When processing continues, Then non-critical failures are captured in error metrics, the record is not written if required fields are invalid, and the batch does not crash.
Sync Health Metrics and Error Reporting
- Given sync activity occurs, When viewing the admin dashboard, Then it displays last successful sync time, next scheduled run, duration, counts of read/created/updated/skipped records, error rate, webhook P95 latency, and retry counts. - Given metrics scraping is configured, When hitting the /metrics endpoint, Then Prometheus-format metrics are exposed over TLS and protected by authentication, returning HTTP 200 within 500 ms. - Given error thresholds are breached (e.g., error_rate > threshold for 2 consecutive runs), When detection triggers, Then alerts are sent within 2 minutes to configured channels with a concise summary and run IDs. - Given an error entry is opened, When inspecting details, Then redacted payload, stack trace, correlation IDs, and retry history are available for triage.
Calendar Locale & Time Zone Detection
"As an engineer, I want my calendar time zone to be recognized so that meetings are scheduled in the right local time even if my HRIS location is outdated."
Description

Connect to Google Workspace and Microsoft 365 to read each user’s calendar time zone and language/region locale with least‑privilege scopes, using settings endpoints only. Use these signals to corroborate or fill gaps when HRIS data is missing or stale. Implement batched polling and change notifications, DST‑aware parsing, and fallbacks when accounts are not connected. Persist provenance (HRIS vs Calendar) and last‑seen timestamps to inform conflict resolution and anomaly scoring. Feed resolved signals into the Locale Mapper pipeline and the scheduling engine.

Acceptance Criteria
Google Workspace least-privilege settings ingestion
Given an org connects Google Workspace, When the OAuth consent screen is displayed, Then only these scopes are requested: openid, email, profile, https://www.googleapis.com/auth/calendar.settings.readonly, and no broader calendar scopes are requested. Given a connected Google user, When ingestion runs, Then only the Google Calendar Settings endpoint is called to read timeZone and locale; no event or calendar list endpoints are called. Then the retrieved timeZone is normalized to a valid IANA ID and locale to a valid BCP 47 tag, and both are persisted with source=GoogleCalendarSettings and a lastSeen timestamp. Then the resolved signals are published to the Locale Mapper within 5 minutes of retrieval.
Microsoft 365 least-privilege settings ingestion
Given an org connects Microsoft 365, When OAuth consent is initiated, Then only these scopes are requested: offline_access and MailboxSettings.Read, and no Calendars.Read or broader scopes are requested. Given a connected Microsoft 365 user, When ingestion runs, Then only the Mailbox Settings endpoint (/me or /users/{id}/mailboxSettings) is called to read timeZone and language; no event or calendar endpoints are called. Then the timeZone is mapped from Windows ID to a valid IANA ID and language to a valid BCP 47 tag, and both are persisted with source=M365MailboxSettings and a lastSeen timestamp. Then the resolved signals are published to the Locale Mapper within 5 minutes of retrieval.
Batched polling and change notifications
Given N users across providers, When the ingestion job runs, Then requests are grouped into batches of up to the configured batch size (default 100) per provider and concurrency does not exceed configured limits. When a provider supports change notifications for settings, Then a subscription is created/renewed and incoming notifications trigger targeted refresh within 2 minutes; when not supported, Then polling occurs at the configured interval. When 429 or 5xx responses are received, Then retries use exponential backoff and honor Retry-After headers, and the job completes within the configured SLA without skipping users. Then users with lastSeen newer than the freshness threshold are skipped to avoid redundant fetches.
DST-aware parsing and normalization
Given any provider-returned time zone identifier, When normalized, Then it resolves to a valid IANA time zone ID or the process fails the record with a retriable error and is logged. Given dates spanning DST transitions for each supported zone, When computing UTC offsets, Then results match tzdb rules with no off-by-one-hour errors. Given ambiguous or non-existent local times at DST boundaries, When normalized, Then storage uses UTC timestamps plus IANA zone and no invalid local time is persisted.
Fallback behavior when accounts are not connected
Given a user without a connected calendar or with revoked tokens, When resolving time zone and locale, Then HRIS values are used if present and fresh within the configured staleness window. When HRIS is missing or stale, Then office assignment-derived time zone is used; if unavailable, Then the organization default is used. Then the chosen fallback is persisted with source=Fallback and a confidence level, and a needsReview flag is set when confidence is below the configured threshold. Then no calls are made to provider APIs for disconnected users.
Provenance, last-seen, and conflict resolution
Given multiple sources (HRIS, Google, M365), When persisting values, Then for each field (timeZone, locale) the system records value, source, lastSeenAt, and freshness state. When sources disagree, Then the resolver applies deterministic precedence: prefer the freshest source within the freshness window; on ties, prefer calendar sources over HRIS; if office location timezone matches one source, prefer that source. Then an anomaly is created when sources differ by >= 2 hours effective offset or have different locale regions, and includes involved sources and timestamps. Then the final resolved value and decision rationale are stored and exposed to the Locale Mapper.
Integration with Locale Mapper and scheduling engine
Given a resolved timeZone and locale for a user, When published, Then Locale Mapper ingests them and marks the user's profile as updated. When a user's timeZone changes, Then the scheduling engine recalculates meeting windows and smart links reflect the new zone within 5 minutes. Then downstream consumers can query the current resolved values via API with provenance and lastSeen metadata.
IANA Time Zone Resolver
"As a scheduling coordinator, I want the system to resolve each person to a precise IANA time zone so that cross‑region windows are accurate year‑round."
Description

Translate HRIS location/office coordinates and calendar locale into a precise IANA time zone (e.g., America/Los_Angeles). Integrate a geocoding and tz lookup service, handle ambiguous city names and border cases, and maintain an up‑to‑date tzdata cache. Produce a confidence score and rationale per assignment, support offline caching, and auto‑adjust for DST rule changes. Expose a deterministic mapping policy and idempotent assignment so downstream scheduling is stable. Provide fallbacks for incomplete addresses using office geofences or IP hints (optional, configurable).

Acceptance Criteria
Address-to-IANA via Geocode and Locale Disambiguation
Rule 1: Given an HRIS record with complete address (city, region, country) and calendarLocale, when resolved, then the IANA time zone returned is valid and matches the tz polygon for the geocoded point; confidence >= 0.98; rationale includes geocodePrecision, matchedCountry, calendarLocale, tzdbVersion, and dataSources. Rule 2: For ambiguous city names (multiple geocode candidates), the resolver selects the candidate whose country == HRIS.country; if still ambiguous, prefers administrativeRegion match; if tie remains, selects candidate whose tz polygon contains the geocode centroid; confidence between 0.90 and 0.97 and rationale lists disambiguation steps. Rule 3: On cache hit (same normalized address within 24h), p95 latency <= 120 ms; on cache miss with live geocode, p95 latency <= 600 ms. Rule 4: Functional accuracy on the regression fixture set (≥500 global addresses) >= 99.0% exact IANA match.
Border Proximity Handling and Anomaly Flagging
Rule 1: Given coordinates within 1 km of any IANA timezone boundary, when resolved, then the returned zone is the polygon that contains the point; rationale includes distanceToBoundary (meters) and boundaryZonePair; confidence >= 0.90 if distanceToBoundary >= 200 m, else confidence between 0.75 and 0.89. Rule 2: If computed confidence < 0.85 OR geocode precision is worse than city-level, then the record is flagged for review with reasonCode in {"NEAR_BORDER","LOW_CONFIDENCE","LOW_PRECISION"} and a suggested next action. Rule 3: Regression tests include at least 50 near-border fixtures across 10 countries; pass rate (correct polygon selection) >= 98%.
Deterministic Mapping and Policy Exposure
Rule 1: Given identical normalized inputs (address fields, office assignment, calendarLocale, featureFlags), repeated resolutions (n=100) return identical outputs: ianaZone string equality, confidence identical to 2 decimal places, and identical rationale fields. Rule 2: Mapping is idempotent: re-running the resolver on an already-assigned record does not alter ianaZone or confidence unless input fields changed. Rule 3: A GET /mapping-policy endpoint returns HTTP 200 with policyVersion (semver), precedenceOrder ["coordinates","officeGeofence","addressGeocode","calendarLocale","ipHint"], tieBreakRules, and tzdbVersion; response is immutable for a given policyVersion. Rule 4: PolicyVersion is included in every resolution rationale and matches the endpoint value.
tzdata Currency and DST Auto-Adjust
Rule 1: System maintains tzdb locally and checks for updates at least weekly; on update, tzdbVersion increases and is logged with timestamp, and resolution responses begin reflecting the new tzdbVersion within 10 minutes. Rule 2: Assigned ianaZone strings remain unchanged across tzdb updates for the same inputs (determinism), while UTC offsets produced for that zone at given timestamps reflect the new tz rules (validated against a curated set of tz change fixtures); fixture pass rate >= 99%. Rule 3: If tzdb update fails, previous version remains active and an alert is emitted; no partial/mixed-version responses are served. Rule 4: Rationale includes tzdbVersion used at resolution time.
Offline Caching and Service Degradation
Rule 1: If external geocoding/tz lookup is unavailable (>=3 consecutive failures or 5xx), resolver switches to offline mode within 30 seconds and uses local cache and office geofences. Rule 2: In offline mode, if a cache entry newer than 30 days exists for the normalized input, that entry is returned with source="cache" in rationale; p95 latency <= 50 ms. Rule 3: If no fresh cache entry exists, resolver attempts officeGeofence; if none applies, returns status="UNRESOLVED" with confidence=null and flags the record for review with reasonCode="SERVICE_DEGRADED". Rule 4: Upon service recovery, system exits offline mode automatically and backfills unresolved items; success rate for backfill >= 99%.
Fallbacks: Office Geofences and Optional IP Hints
Rule 1: For incomplete addresses (missing city or country), resolver applies precedence: officeGeofence -> addressGeocode (partial) -> calendarLocale -> ipHint (only if feature flag ipHintsEnabled=true). Precedence is reflected in rationale.fallbackChainUsed. Rule 2: Office geofences are polygons with named zones; when a user’s office assignment point-in-polygon matches, the mapped IANA zone is returned with confidence >= 0.95 and rationale.source="officeGeofence". Rule 3: If calendarLocale is used, the resolver maps locale to a country default zone only when the country has a single practical business zone in the policy; otherwise the record is flagged for review with reasonCode="LOCALE_AMBIGUOUS". Rule 4: If ipHint is used, rationale includes ipAsn, ipCountry, and ipConfidence; overall confidence must be <= 0.85 and the record is marked requiresReview=true. Rule 5: Fallback decisions are deterministic for identical inputs and feature flags.
Holiday Set Assignment & Overrides
"As a team lead, I want holiday calendars assigned automatically with the ability to override edge cases so that we avoid booking on local holidays."
Description

Automatically assign national and regional holiday calendars based on resolved location, with support for multi‑region users, remote employees, and company‑defined holidays. Source authoritative holiday datasets, refresh annually and on late‑breaking changes, and localize names. Allow admins to set precedence rules (company > region > country), apply per‑user or team overrides, and preview effects. Persist holiday set provenance and surface assignments to the scheduling engine to prevent after‑hours or holiday booking. Include API and UI hooks for manual adjustments and audit logging.

Acceptance Criteria
Automatic Holiday Set Assignment from Resolved Location
Given a user has a resolved location with country and regional codes When holiday assignment runs (via nightly job or on-demand re-evaluation) Then the user is assigned the matching country and regional holiday sets and the assignment is marked active with provenance=location_resolver Given a user has a resolved country but no mapped regional set When holiday assignment runs Then only the country holiday set is assigned and a note reason="no_regional_set" is stored in provenance Given a user has missing or conflicting location signals When holiday assignment runs Then the user is flagged review_required and no new holiday sets are applied Given assignment completes When viewing the user’s holiday settings Then the effective sets, their sources, dataset versions, and last_updated timestamps are displayed
Admin-Configurable Holiday Precedence and Company Holidays
Given precedence rules are configured as company > region > country When a date is a company holiday but not a regional or country holiday Then the date is treated as a holiday for all affected users Given precedence rules are re-ordered by an admin When the admin previews changes Then the preview shows net additions/removals of holidays per user/team and does not persist changes Given a company-defined holiday is created with localized names When users view the holiday in different locales Then the holiday name is displayed in the user’s locale with fallback to English
Multi-Region Users and Team Overrides
Given a user is assigned to multiple regions or teams with distinct holiday sets When effective holidays are computed Then the effective holiday calendar is the union of all assigned sets unless an explicit override policy is set to prioritize a specific set Given a team-level holiday set override is applied When the override is saved Then all current team members inherit the set unless a per-user override exists and audit entries are recorded for each affected user Given a per-user override is added or removed When saved Then effective holidays recalculate immediately and a provenance entry indicates source=manual_override
Authoritative Dataset Sourcing, Refresh, and Localization
Given authoritative holiday datasets are configured When the annual refresh job runs by 00:00 UTC on Jan 1 Then the system ingests the new year’s data, versions the dataset, and re-evaluates assignments Given a provider issues a late-breaking change to an existing year When the change is detected Then updates are propagated within 24 hours, impacted users are re-evaluated, and a change log is recorded Given users have different locale settings When holiday names are displayed Then names are localized to the user’s locale with fallback to English and include region qualifiers where applicable
Manual Adjustments via UI and API with Audit Logging
Given an admin with Holiday:Manage permission When they add or remove a holiday set for a user via the UI Then the change applies immediately, provenance=manual_ui is recorded, and an audit log captures who/when/what with before/after values Given a client calls PATCH /users/{id}/holiday-sets with a valid payload When the request is processed Then the response is 200 OK with the updated effective sets, the operation is idempotent, and invalid codes return 400 with error details Given an admin requests a preview of changes When using the UI or GET /holiday-sets/preview with proposed rules Then the API returns the computed deltas and impact summary without persisting changes
Scheduling Engine Integration and Enforcement
Given participants have effective holiday calendars When the scheduling engine generates suggested meeting slots Then any slots falling on a holiday for any participant are excluded from suggestions Given a host attempts to book on a participant’s holiday via a smart link When selecting a time Then the UI prevents selection and displays the holiday name and source (company/region/country/manual) Given holiday assignments change for a user When the cache invalidation job runs Then scheduling reflects the changes within 5 minutes and next suggestions exclude newly added holidays
Anomaly Detection & Review Queue
"As an admin, I want to be notified and quickly resolve timezone or holiday anomalies so that errors don't cascade into bad scheduling."
Description

Implement rules and heuristics to flag discrepancies between HRIS location, calendar time zone, recent meeting offsets, and travel status (if available). Score anomalies by severity (e.g., HRIS says Paris, calendar says PST) and route them to a review queue with suggested fixes and confidence levels. Provide filters, search, and notifications (email/Slack) for admins, plus one‑click apply/ignore actions and bulk operations. Track resolution outcomes and feed them back into the model to reduce false positives. All actions are auditable and reflected immediately in scheduling.

Acceptance Criteria
High-Severity Time Zone Mismatch Flagging and Scoring
Given a person whose HRIS location resolves to Europe/Paris And their primary calendar time zone is America/Los_Angeles And recent meeting offsets over the last 14 days align with America/Los_Angeles within ±1 hour When anomaly detection runs Then an anomaly record is created for the person And the anomaly type is "TZ_MISMATCH" And the severity score is >= 80 on a 0–100 scale And the suggested fix is "Set time zone to America/Los_Angeles" And the confidence is >= 0.8 And the anomaly appears in the review queue within 1 minute of detection
Review Queue: Suggested Fixes with Confidence
Given at least one anomaly exists When an admin opens the review queue Then each anomaly row displays person name, anomaly type, severity (0–100), suggested fix, confidence (0.00–1.00), detected_at timestamp, and current status And the row includes source signals summary (HRIS location, calendar TZ, recent offsets, travel status if available) And rows are sortable by severity and detected_at And opening the detail view shows the full suggested fix payload and confidence value
Admin Filters and Search on Review Queue
Given 500 or more anomalies exist across teams When the admin applies filters for severity range, anomaly type, source discrepancy, status (new/applied/ignored), detected date range, team, and assignee Then the list updates to reflect the filters within 2 seconds And the result count matches the filtered set When the admin searches by person name or email substring Then results include matching anomalies across all pages And an empty state is displayed when zero results match
Email and Slack Notifications for New Anomalies
Given an admin has enabled notifications and configured a Slack channel and email And a new anomaly with severity >= 70 is created When detection completes Then the admin receives a Slack message within 5 minutes including total new anomaly count, top 3 items by severity, and a deep link to the queue with the severity filter pre-applied And the admin receives an email with the same information within 5 minutes And disabling notifications suppresses both channels And daily digest mode sends a single summary for anomalies detected in the prior 24 hours at the configured delivery time
One-Click Apply/Ignore and Bulk Operations
Given an admin selects a single anomaly When they click "Apply Fix" Then the suggested fix is applied to the person (time zone and/or holiday set) And the anomaly status becomes "applied" with applied_by and applied_at recorded And the person's scheduling context is updated immediately Given an admin selects 100 anomalies via multi-select When they choose Bulk Apply Fix or Bulk Ignore and confirm with an optional reason Then the system processes the bulk action with progress feedback And completes within 30 seconds for 100 items And partial failures are reported per item with a retry option And repeating the same bulk action within 10 minutes is idempotent (no duplicate changes)
Audit Logging and Immediate Scheduling Reflection
Given any apply or ignore action occurs (single or bulk) When an auditor views the audit log Then each entry includes actor, UTC timestamp, affected user(s), fields before/after, action type, reason (if provided), and source signals snapshot And audit entries are immutable and retained for at least 13 months And changes are queryable by actor, person, and date range When a fix is applied Then scheduling endpoints reflect the new time zone/holiday set within 30 seconds And existing smart links and scheduling suggestions use the updated settings without manual refresh
Outcome Tracking and Model Feedback Suppression
Given anomalies are resolved as applied or ignored with a reason When the feedback processor runs Then the resolution outcome is persisted and linked to the anomaly pattern And identical patterns for the same person are suppressed for 30 days after an "Ignore" resolution And a metrics view displays false-positive rate (ignores/total flags) for the last 30 days And re-evaluating the person's signals after an "Apply" resolution does not recreate the same anomaly
Admin Review & Bulk Resolution UI
"As an operations manager, I want a fast review workflow with bulk actions so that I can resolve mapping issues for the entire org in minutes."
Description

Deliver an in‑app interface for Locale Mapper that displays each user’s current assignment with side‑by‑side source data (HRIS, Calendar, manual), confidence scores, and change history. Enable bulk selection and application of recommended fixes, manual time zone/holiday selection with typeahead, and reason codes. Enforce role‑based access controls, provide keyboard shortcuts, accessibility compliance, and responsive performance for orgs with 10k+ users. Changes propagate to the scheduling engine and are logged for audit and export.

Acceptance Criteria
User Row Data Completeness & Accuracy
Given an authenticated Locale Admin views the Admin Review table When a user row is rendered Then the row shows current Time Zone (IANA) and Holiday Set And a side-by-side panel lists source values from HRIS, Calendar, and Manual with source labels, last-updated timestamps, and source IDs And a confidence score (0–100%) is displayed for the current assignment And a Change History link opens a modal with chronologically ordered entries showing actor, before/after values, reason code, timestamp, and request ID And all displayed values match the backend API for that user at request time And the first 50 rows render within 2 seconds for an org of 10k users (cold cache) p95 And loading the next 50 rows via pagination or infinite scroll completes within 500 ms p95
Bulk Apply Recommended Fixes at Scale
Given an Admin filters to Anomalies and selects N users (up to 5,000) When Apply Recommended is confirmed Then a confirmation dialog summarizes counts by change type and expected impact And the bulk operation runs asynchronously with a visible progress indicator and cancel option And successes and failures are reported with per-user reasons; partial failures do not block others And the operation applies at least 95% of eligible changes within 2 minutes for N=5,000 p95 And a CSV of failures is downloadable upon completion or cancel And each success produces an audit entry with reason code "System Recommendation" And no changes occur if the Admin cancels at the confirmation step
Manual Override via Typeahead with Reason Codes
Given an Admin opens edit on a user When typing in the Time Zone field Then typeahead returns matching IANA zones and common aliases within 150 ms p95 and selection is required And when typing in the Holiday Set field Then typeahead returns available holiday calendars within 150 ms p95 And saving requires a Reason Code from a predefined list or Other with free text (1–250 chars) And invalid or unsupported values are blocked with inline error messaging and no data is saved And multi-select edit enforces the same validation across all selected users And upon save the UI shows a pending state and reflects new values only after server confirmation
Anomaly Flagging, Filtering, and Selection
Given Locale Mapper has computed anomalies When the Admin opens the Review queue Then each anomalous row displays a visible flag and tooltip explaining the condition (e.g., HRIS vs Calendar mismatch or confidence < 80%) And the filter Anomalies only reduces the list accordingly and shows a count badge And sorting by Lowest confidence orders rows ascending by confidence score And Select all offers both Select all on this page and Select all X users in current filter And bulk selection state persists across pagination until cleared
Role-Based Access Control and Permissions
Given a user without Locale Admin permission visits the Admin Review URL Then the UI shows an access denied message and no user data is rendered, and a 403 is returned by the API And API endpoints for list, edit, bulk apply, and export enforce the same RBAC checks And users with Viewer permission can view data but all edit and bulk controls are disabled and actions are blocked server-side And all access checks are logged with user ID, role, endpoint, and timestamp
Keyboard Shortcuts and Accessibility Compliance
Given a keyboard-only user navigates the interface When interacting with the data grid and dialogs Then focus order is logical, visible focus indicators are present, and no keyboard traps exist And shortcuts work: Space (toggle row select), Enter (open details), Ctrl/Cmd+A (select all in view), Ctrl/Cmd+Enter (confirm bulk apply), Ctrl/Cmd+S (save edit) And all interactive elements have accessible names and ARIA roles; status updates (e.g., bulk progress) are announced via live regions And the UI meets WCAG 2.2 AA for contrast and non-text contrast And screen readers correctly read column headers, flags, validation errors, and progress
Change Propagation to Scheduling Engine & Audit Export
Given a change (bulk or manual) is saved successfully When propagation to the scheduling engine begins Then updated time zone and holiday set are reflected in the scheduling engine within 60 seconds p95 And transient failures are retried up to 3 times with exponential backoff and idempotent requests (no duplicate application) And each change writes an immutable audit record containing user ID, actor, actor role, before/after values, reason code, ISO 8601 UTC timestamp, and request ID And Admins can export audit logs filtered by date range and user in CSV and JSON; exports for 30 days complete within 2 minutes p95
Privacy & Compliance Safeguards
"As a security‑conscious admin, I want minimal data collection and clear controls so that we meet compliance and protect employee privacy."
Description

Apply least‑privilege scopes and data minimization across HRIS and calendar integrations, storing only fields required for locale mapping. Encrypt data in transit and at rest, support data residency where applicable, and provide admin controls for consent, retention, export, and deletion (GDPR/CCPA). Redact PII in logs, segregate tenant data, and expose audit trails for access and changes. Document scopes and purposes in‑product and provide a security review pack for enterprise customers.

Acceptance Criteria
Least-Privilege Scopes & Data Minimization
- Integration requests include only scopes required to read HRIS location/office and calendar time zone/locale; no access to event content, emails, or contacts. - Allowed stored fields are limited to: internal_user_id, external_user_ref, time_zone, locale, country_code, office_id, holiday_set_id, data_source, residency_region, last_synced_at; all other fields are discarded at ingestion. - Any attempt to request additional scopes is blocked, surfaced to admins for approval, and recorded in audit logs. - A data inventory report in Admin > Privacy lists all collected fields per integration and shows “Not Collected” for PII (name, email, phone, address, event titles/bodies, attendees). - Unit/integration tests assert that event/read scopes are not requested for Google/Microsoft; HRIS SDK calls exclude sensitive fields (compensation, national_id, DOB).
Encryption in Transit and At Rest
- All client and partner API traffic enforces TLS 1.2+ with HSTS; HTTP requests are rejected with 403 and no data processed. - All at-rest tenant data and backups are encrypted with AES-256 using cloud KMS-managed keys; key rotation occurs at least every 90 days. - Secrets are stored only in a managed secrets vault; plaintext secrets in config or logs are blocked by CI checks. - Key access and use are auditable; KMS access logs show only authorized service principals within the last 90 days. - Pen test/automated OWASP checks show no findings for insecure transport or unencrypted storage at release time.
Data Residency Controls
- Admin can select a residency region (e.g., EU, US) during onboarding; the chosen region is displayed in Admin > Privacy and locked to that tenant. - All tenant data and backups are stored exclusively in the selected region; cross-region writes are blocked by policy. - Residency choice and any attempted changes are captured in audit logs with actor, timestamp, and IP. - Region-specific S3/Blob buckets and databases are provisioned per tenant; automated tests verify object and record locality by ARN/endpoint. - If a requested region is unavailable, the UI informs the admin and prevents provisioning until a supported region is chosen.
Admin Controls: Consent, Retention, Export, Deletion
- Before connecting HRIS/Calendar, admins must review a consent screen listing exact scopes and purposes and explicitly approve; approval is timestamped and logged. - Admin can revoke consent at any time; syncing stops within 5 minutes and previously synced data is purged within 24 hours, with purge receipts in audit logs. - Admin can configure data retention (30–365 days); data older than the configured TTL is permanently deleted by a daily job and deletions are logged with counts. - Admin can export all locale-mapping data for a selected user or the entire tenant in machine-readable JSON within 15 minutes; exports exclude redacted PII. - Admin can delete a selected user’s data; API/UI returns confirmation within 2 minutes and audit log shows actor, scope (user), and deletion outcome.
PII Redaction in Logs & Telemetry
- Application, access, and integration logs never contain names, emails, event titles/bodies, addresses, phone numbers, or raw HRIS payloads; such fields are replaced with [REDACTED] or irreversible hashes. - Redaction is enforced via middleware applied to all inbound/outbound integration handlers; unit tests provide sample payloads proving removal/masking. - Observability tools (traces/metrics) exclude PII by schema; CI fails builds if blocked fields are detected via static log scanning. - Support/debug bundles automatically scrub PII before download; a preview shows redacted fields. - Log retention is configurable and separated from tenant data; access to logs is restricted to authorized roles and recorded in audit logs.
Tenant Segregation and Audit Trails
- All data access is tenant-scoped by tenant_id at the storage and service layers; cross-tenant reads/writes return 403 and are captured as security events. - RBAC enforces least privilege: only Tenant Admins can view privacy settings, audit logs, or trigger exports/deletions; regular users cannot access these endpoints. - Every read/write to locale-mapping data records an immutable audit entry with actor, action, target (user/tenant), timestamp, and source IP; logs are retained for at least 365 days. - Admins can filter, search, and export audit logs by actor, action, and date; exported logs are delivered within 10 minutes. - Automated isolation tests and canary tenants validate no data leakage across tenants on each deployment.
In‑Product Scope & Purpose Disclosure and Security Review Pack
- An in-product page lists all requested scopes per provider (HRIS/Calendar) with plain-language purposes mapped to the Locale Mapper; page is accessible pre-connection. - Any scope changes trigger a versioned changelog, notify tenant admins, and require explicit re-consent before sync resumes. - Enterprise admins can download a current security review pack (SOC 2 status/letter, data flow diagram, subprocessor list, DPA template) from the admin portal; links are live and dated within the last 12 months. - The disclosure page and review pack links are covered by uptime monitoring; broken links or stale documents raise alerts within 15 minutes. - Content tests verify that listed scopes match those configured in the integration manifests at build time.

Starter Kits

Role- and industry-based presets (Engineering standups, Sales pods, CS onboarding, Recruiting panels) pre-paint safe windows and focus-block defaults. Start booking with battle-tested setups instead of building from scratch.

Requirements

Starter Kit Library & Discovery
"As a remote team lead, I want to browse and preview recommended starter kits for my role and regions so that I can start booking with sensible defaults without manual setup."
Description

Provide a browsable library of role- and industry-based presets with searchable filters (role, function, industry, team size, regions, meeting type) and inline previews of pre-painted windows and focus-block defaults on the Timeglue timeline. Integrate with connected calendars to surface potential conflicts (existing focus blocks, recurring meetings) in previews without saving changes. Support metadata (name, description, tags, target roles, supported regions), localization of labels, and multi-tenant scoping. Expose a backend endpoint for fetching kit metadata and assets, and a performant client-side experience with typeahead search and quick-compare of multiple kits. Expected outcome: users find an appropriate kit in seconds, reducing setup time and eliminating guesswork.

Acceptance Criteria
Typeahead Search and Faceted Filters for Starter Kit Library
Given I am on the Starter Kit Library, when I type at least 2 characters into the search box, then typeahead suggestions appear within 200 ms at P95 and highlight matched terms across name, tags, target roles, and industries. Given I select any combination of facets (role, function, industry, team size, regions, meeting type), when I apply filters, then the results reflect the intersection of selected values and the result count updates instantly (<100 ms after data is local). Given I paginate or infinite scroll, when I reach the end of the current page, then the next page loads within 300 ms at P95 without blocking input and preserves scroll position. Given I share or reload a URL containing q and active filters, when the page loads, then the search box and facets are restored from the URL and results are identical to the sharer’s view. Given filters produce zero results, when the list is empty, then an empty state is shown with a clear-filters action and no errors are thrown.
Inline Timeline Preview of Pre‑Painted Windows and Focus Blocks
Given a kit card is visible, when I click Preview, then the Timeglue timeline renders that kit’s pre‑painted meeting windows and focus‑block defaults within 500 ms at P95 using my local timezone. Given preview mode is open, when I close or navigate away, then no settings are saved and no calendar events are created, modified, or deleted. Given a kit defines multiple supported regions, when I change the reference region, then the timeline reflows window offsets correctly for the selected region without a full page reload. Given keyboard-only navigation, when I tab through preview controls, then all interactive elements are reachable, focus indicators are visible, and ARIA labels describe actions (Open Preview, Close, Change Region).
Calendar Conflict Overlays in Preview (Non‑Destructive)
Given my calendars are connected, when I open a kit preview, then existing focus blocks and recurring meetings over the next 4 weeks are overlaid as conflicts on the timeline without creating or editing any events. Given I have multiple calendars, when I toggle calendar visibility in preview, then only selected calendars’ conflicts are shown. Given calendar authorization is missing or revoked, when I open preview, then a non-blocking notice is shown and preview still loads without conflict overlays. Given privacy settings are enabled to obfuscate titles, when conflicts render, then event titles are masked accordingly and are not exposed outside my tenant. Given a Show Conflicts toggle is off, when I view preview, then no conflict overlays appear.
Quick‑Compare Up to Three Kits Side‑by‑Side
Given I select Compare on kits from the library, when I choose up to three kits, then a side‑by‑side timeline appears with aligned hour scales and distinct labels/colors within 700 ms at P95. Given I attempt to add a fourth kit to compare, when I click Compare on another kit, then the UI prevents the addition and explains the 3‑kit limit via tooltip or toast. Given I change search terms or filters while comparing, when any compared kit drops out of the result set, then it is removed from the compare view with a notice and remaining kits persist. Given I exit compare mode, when I return to the library, then my prior scroll position and filter state are preserved.
Metadata Completeness and Localization
Given a kit detail panel is opened, then the name, description, tags, target roles, supported regions, and meeting type are displayed and adhere to the metadata schema; missing optional fields show a localized fallback label (e.g., Not provided) instead of blank. Given my app locale is one of en, es, fr, de, or ja, when I change locale, then facet labels, UI copy, and region names localize; kit descriptions use localized variants when available, else fall back to English. Given an RTL locale (e.g., ar) is enabled, when I view the library and preview, then layout mirrors appropriately and text direction is RTL without visual overlap. Given dates and numbers are rendered, when locale changes, then formatting follows the locale’s conventions.
Multi‑Tenant Scoping and Data Isolation
Given I am authenticated as a user in tenant T, when I view the Starter Kit Library, then only kits with scope=global or scope=tenant(T) are returned in both UI and API. Given I use search and typeahead, when suggestions appear, then they never include kits scoped to other tenants. Given an API request is made without tenant context or with invalid credentials, when accessing kit resources, then the response is 401 (unauthorized) or 403 (forbidden) and no kit data is returned. Given analytics and logs are produced, then no tenant‑scoped kit identifiers or metadata are exposed to other tenants.
Backend API for Kit Metadata and Assets
Given the client issues GET /api/kits with query params (q, role, function, industry, team_size, regions, meeting_type, sort, page, page_size), when params are valid, then the API returns 200 with JSON { items:[], total, page, page_size } including required metadata fields and asset references. Given page_size is not specified or exceeds 50, when the request is processed, then page_size defaults to 20 and is capped at 50. Given the request includes If-None-Match with a current ETag, when content is unchanged, then the API returns 304 without a body. Given tenant scoping is enforced via auth token, when the token lacks access to a kit, then that kit is excluded from results. Given a large catalog (≈5,000 kits) and warm cache, when fetching a filtered page, then P95 latency is ≤200 ms and P99 ≤350 ms; responses are gzip/brotli compressed with Cache-Control set for 5 minutes.
Preset Configuration Schema & Versioning
"As a product admin, I want starter kits to be defined by a robust, versioned schema so that they are reliable, localizable, and easy to evolve without breaking existing teams."
Description

Define a robust, versioned schema to represent a starter kit’s defaults: regional working hours, cross-time-zone acceptable meeting windows, holiday locale packs, focus block patterns (frequency, duration, recurrence, no-meeting days), meeting type defaults (durations, buffers, lead time, max per day), and invite guardrails (after-hours prevention, escalation rules). Include human-readable metadata, tags, and compatibility markers (supported locales, time zones). Implement strict validation, backward compatibility rules, and migration utilities so existing teams are not broken by updates. Store in a centralized repository with read APIs, caching, and rollback support. Enable localization of text and regional holiday mappings. Expected outcome: stable, extensible kits that can evolve safely and be shared across organizations.

Acceptance Criteria
Schema Validation and Error Reporting for Starter Kits
Given a starter kit JSON payload declaring schemaVersion=v1.0 When it is POSTed to POST /kits Then the payload is validated against the declared schema version and any unknown properties, type mismatches, or missing required fields (including metadata.name, metadata.tags[], compatibility.supportedTimeZones[], compatibility.supportedLocales[]) cause HTTP 422 with a machine-readable errors[] list using JSON Pointers to offending paths And a valid payload returns HTTP 201 with kitId, schemaVersion echoed, and server-normalized defaults applied (durations, buffers, recurrence)
Backward-Compatible Loading and Non-Breaking Evolution
Given an organization with existing kits saved under schema v1.2 And a platform upgrade introduces schema v1.3 with backward-compatible changes When GET /kits/{id} is called for a v1.2 kit Then the response succeeds with HTTP 200 and includes migrationInfo.sourceVersion=v1.2 and targetVersion=v1.3 And the returned document is in v1.3 shape without data loss or field renaming that changes semantics And scheduling outcomes for a fixed regression week (windows, max-per-day, buffers, guardrails) are identical before vs after upgrade
Transactional Migration and Rollback
Given a repository configured for versioned kit storage When an operator runs a migration from v1.2 to v1.3 Then the migration executes atomically so no partial updates are visible to readers And migration is idempotent when re-run with the same inputs And a rollback command restores all affected kits to the prior version within 5 minutes and emits an audit log entry with actor, timestamp, and counts And if any step fails, the migration aborts and repository state remains unchanged
Localization and Regional Holiday Mapping
Given localization bundles for en-US, fr-FR, and es-ES and holiday packs for US-CA, FR, and ES When a kit specifies display.locale=fr-FR and holidays=[FR] Then all human-readable fields resolve to fr-FR keys with fallback to en-US for missing keys And holidays in FR are excluded from acceptable windows for the next 12 months in Europe/Paris And switching locale to es-ES with holidays=[ES] updates text and excludes ES holidays without changing the underlying UTC windows except for localized date boundaries
Cross-Time-Zone Window Computation and Constraints
Given a kit defining regional work hours (NY 09:00–17:00, London 09:00–17:00) and focus blocks Tue/Thu 13:00–15:00 local And meeting type defaults duration=30m, bufferAfter=10m, leadTime=24h, maxPerDay=4 When participants are in America/New_York and Europe/London and windows are computed for the week of 2025-10-06 Then acceptable windows intersect only within both regions' work hours excluding focus blocks and local holidays And no proposed slot violates leadTime And buffers ensure no overlap between consecutive slots And no more than 4 meetings are schedulable per participant per day
Read API Caching, ETag, and Invalidation
Given a published kit at /kits/{id} When a client GETs the kit Then the response includes strong ETag and Last-Modified and Cache-Control: max-age=300, public And a subsequent GET with If-None-Match returns HTTP 304 when unchanged And after publishing a new kit version, cache is invalidated and GET returns HTTP 200 with a new ETag within 1 second And p95 latency for GET /kits/{id} with warm cache is ≤150ms under 100 RPS in staging
Invite Guardrails and Escalation Encoding
Given a kit with after-hours prevention enabled and escalationRules.allowOverrideWithManagerApproval=true When a scheduling request proposes a slot outside a participant’s defined work hours Then the API rejects the proposal with HTTP 409 and reason=AFTER_HOURS and includes the nearest acceptable alternatives And when an override token signed by an authorized manager is supplied, the request succeeds and the event is flagged guardrailOverridden=true in the audit log
Apply Kit to Team/Workspace with Merge & Rollback
"As a workspace admin, I want to apply a starter kit across my team with clear conflict handling and the option to undo changes so that rollout is safe and predictable."
Description

Enable admins to apply a selected starter kit to a workspace, team, or individual users with a guided flow that shows a diff between current settings and the kit. Detect and resolve conflicts (e.g., overlapping focus blocks, incompatible work hours) with selectable merge strategies (replace, merge, skip). Support batch application to multiple members, permission checks, and dry-run preview. Apply changes transactionally with full rollback and audit logging. Trigger recalculation of smart booking links, meeting guardrails, and notifications summarizing changes to affected users. Expected outcome: safe, predictable rollouts that respect existing settings while standardizing scheduling norms.

Acceptance Criteria
Permission Checks and Scope Selection
Given I am signed in as a non-admin When I navigate to Starter Kits > Apply Kit Then I see an authorization error state and cannot proceed Given I am signed in as a workspace admin When I open the Apply Kit flow Then I can choose a target scope of Workspace, Team(s), or User(s) And the Team selector lists only teams where I have admin privileges And the User selector lists only members of the workspace And the Next button remains disabled until at least one valid target is selected
Guided Diff Preview for Selected Kit and Scope
Given I am a workspace admin, have selected a starter kit, and have chosen one or more targets (Workspace/Team(s)/User(s)) When I proceed to the Review & Diff step Then a categorized diff is displayed showing counts of Additions, Modifications, and Removals per target and in total And each change item shows its category (Work Hours, Focus Blocks, Holidays, Smart Link Presets, Guardrails) and impacted target(s) And unchanged items are hidden by default with an option to Show Unchanged And the Apply button remains disabled until any required merge strategy decisions are confirmed or defaults are accepted
Conflict Detection and Merge Strategy Resolution
Given the diff contains conflicts (e.g., overlapping focus blocks, incompatible work hours, duplicate holidays/presets with differing definitions) When I choose a merge strategy at category or item level (Replace, Merge, Skip) Then the preview updates immediately to reflect the chosen strategy And Replace overwrites the target's values for that category/item with the kit's values And Merge preserves existing values, adds kit values that do not introduce overlaps or policy violations, and for conflicts: - Focus Blocks: keep existing; exclude overlapping portions of kit blocks - Work Hours: retain existing hours; add kit hours only if within org policy - Holidays/Presets: union by name with de-duplication, preferring existing definitions on conflict And Skip leaves the target's values unchanged for that item And Apply is enabled only when all conflicts are resolved or explicitly skipped
Dry-Run Mode Produces No Side Effects
Given I toggle Dry Run in the Apply Kit flow When I execute the dry-run Then no settings are persisted for any target And no recalculation jobs are enqueued And no user notifications are sent And a summary report is shown with the number of targets evaluated, counts of proposed Additions/Modifications/Removals, and a list of unresolved conflicts (if any)
Transactional Apply with Full Rollback and Audit Logging
Given I start an Apply operation for N authorized targets with selected strategies When the system commits the changes Then either all targeted changes are committed successfully or none are committed And on any failure during commit, the system rolls back all interim changes to the exact pre-apply state And an audit log entry is recorded containing correlation ID, actor, timestamp, scope and targets, kit name and version, selected strategies, counts of changes, result (Committed or Rolled Back), and error details if rolled back And the UI displays the outcome and correlation ID to the admin
Batch Application Pre-Validation for Multiple Members
Given I select M users across K teams as targets When I run pre-validation Then the system validates permissions, policy constraints, and potential conflicts per target And unauthorized or invalid targets are listed with reasons and excluded by default from the apply set And I can proceed only with the authorized subset, with the authorized count clearly displayed And the final Apply executes transactionally across the authorized subset
Post-Apply Recalculation and User Notifications
Given an Apply operation completes successfully (not a dry-run) When the commit finishes Then smart booking links and meeting guardrails are recalculated for all affected targets within 60 seconds And affected users receive a notification summarizing what changed (categories altered, effective date/time, initiator) within 5 minutes And notifications respect each user's delivery preferences (email, in-app) and locale And a link to the corresponding audit log entry is included in the notification
Kit Customization & Save-as Template
"As a team lead, I want to tweak a starter kit and save it as our team’s template so that it matches our specific schedule norms."
Description

Allow users with appropriate permissions to clone a starter kit and customize parameters: adjust painted meeting windows, focus block rules, holiday sets, and meeting-type defaults. Support saving as an organization template with visibility controls (org-wide, team, private), change notes, and version history. Maintain lineage to the upstream kit version and surface update suggestions when the base kit evolves. Provide export/import of kit JSON for portability. Expected outcome: organizations can tailor best-practice presets to their specific norms without losing upgradeability.

Acceptance Criteria
Clone Starter Kit with Permissions
- Given a user with Manage Kits permission, when they view a starter kit, then the "Clone & Customize" action is visible and enabled. - Given a user without Manage Kits permission, when they view a starter kit, then the action is not available (or disabled with tooltip "Insufficient permissions") and attempting a deep link to clone returns HTTP 403. - When cloning is initiated, then a draft custom kit is created within 2 seconds, pre-populated with all parameters from the base kit's current version, and lineage metadata is recorded: baseKitId, baseVersion, clonedBy, clonedAt. - Then the draft name defaults to "<Starter Kit Name> — Custom" and must be unique within the organization; if not unique, the user is prompted to provide a unique name before saving.
Customize Scheduling Parameters
- Given a draft custom kit, when the user adjusts painted meeting windows on the timeline, then changes render immediately (<300ms) and persist in the draft. - When the user defines focus block rules (recurrence, duration, days), then overlaps with meeting windows are flagged with inline errors, and the Save button is disabled until resolved. - When the user selects a holiday set (org default, regional, custom), then the preview marks those days as non-bookable. - When editing meeting-type defaults (duration, buffers, max per day, lead time), then invalid values are blocked with validation messages and valid changes are saved. - After saving and reloading the editor, all modified parameters are restored exactly as saved.
Save as Template with Visibility Controls
- Given a valid draft, when the user selects "Save as Template," then they must choose visibility: Org-wide, Team (with team selection), or Private; only teams the user belongs to are selectable. - When saving, a change note is required (1–500 characters). - Then the template is created within 2 seconds with attributes: visibility, owningTeamId (if Team), createdBy, createdAt, and version=1. - Then access is enforced: Org-wide is discoverable by all org users; Team-visible is discoverable by members of the selected team; Private is visible only to the creator and org admins. - Then the new template appears in the template library with the chosen visibility filter applied.
Version History and Change Notes
- Given an existing template, when the user saves edits with a change note, then a new sequential version is created (version N+1) with timestamp, author, and note. - When viewing version history, then the user can see a list of versions with notes and compare any two versions to see parameter-level diffs (windows, focus rules, holiday set, meeting-type defaults). - When reverting to a prior version, then a new version is created that matches the selected version's parameters and records a system-generated note "Revert to vX". - History is immutable: previous versions remain viewable and cannot be edited.
Upstream Lineage and Update Suggestions
- Given a template cloned from a starter kit, when the upstream starter kit publishes a newer version, then within 1 hour the template shows an "Updates available" banner indicating base version -> latest version. - When the user opens the update flow, then a diff summarizes impacted parameters and offers per-parameter choices: Accept upstream change or Keep local. - When the user applies updates, then a new template version is created with lineage updated to the upstream latest version and a change note auto-prefilled "Merged upstream vX->vY". - If the template has no upstream lineage, then no update banners or flows are shown. - If all upstream changes are orthogonal to local edits, then the system applies updates without conflicts; otherwise, conflicts require explicit user choice per parameter before saving.
Export and Import Kit JSON
- Given a template, when the user selects Export JSON, then a file is downloaded within 2 seconds containing schemaVersion, template parameters, version metadata (summaries), and lineage metadata. - When importing a kit JSON, then the file is validated against the schema; on failure, import is blocked with actionable errors; on success, the user must select visibility (Org-wide, Team they belong to, or Private). - If the JSON references a team or holiday set not present in the org, then the user is prompted to map to an existing resource or default to Private and the org default holiday set. - Import always creates a new template with a new ID (never overwrites), preserves sourceCreatedAt in metadata, and appears in the library after completion. - After import, the template parameters match the exported snapshot (verified by comparing a hash/checksum included in metadata).
Smart Booking Links Preloaded with Kit Defaults
"As a recruiter, I want booking links that reflect our starter kit’s safe windows so that candidates can only pick times that work for our panel without back-and-forth."
Description

Generate shareable booking links that encode the selected kit’s safe windows, focus block enforcement, buffers, durations, and lead-time constraints. Ensure recipients view availability in their local time zone while respecting the sender’s after-hours guardrails. Support deep links to a timeline preview, optional parameter overrides as allowed by the template, link expiration, and revocation. Tag bookings with the originating kit for attribution. Expected outcome: instant, back-and-forth-free scheduling that adheres to standardized team norms.

Acceptance Criteria
Generate Booking Link with Selected Kit Defaults
Given an authenticated user selects a Starter Kit When they click "Create Smart Link" Then a link is generated containing the kit’s safe windows, focus-block enforcement, meeting duration, buffer times (before/after), and min/max lead-time constraints And the link payload references the kit ID and kit version And opening the link shows availability derived from these encoded defaults without requiring the creator to be logged in
Recipient Local-Time Availability with Sender Guardrails
Given a sender with defined work hours, holidays, safe windows, and focus blocks in TZ A and a recipient in TZ B When the recipient opens the smart link Then all times display in TZ B And no times outside the sender’s work hours, safe windows, holidays, or focus blocks are offered And times violating minimum or maximum lead time are excluded And buffers are honored such that adjacent bookings cannot violate pre/post buffer requirements And times across DST transitions show correct local times while remaining within sender guardrails
Template-Allowed Parameter Overrides
Given a Starter Kit that allows overriding duration (15–60 minutes) and buffers (0–15 minutes) but disallows changes to safe windows and focus blocks When the link is opened with overrides within allowed bounds (e.g., duration=45&buffer_before=10) Then availability reflects those overrides When the link includes overrides outside allowed bounds or for restricted fields Then the system ignores those overrides and applies the template defaults And the booking confirmation records the effective duration and buffers used
Timeline Preview Deep Link
Given a smart link that includes preview=1 When the link is opened Then a timeline preview renders with painted safe windows and focus blocks that exactly match the link’s effective constraints And switching between day and week views preserves the same availability And clicking "Book" from the preview uses the same constraints for the final scheduling flow
Link Expiration Enforcement
Given a smart link with an expiration timestamp set in UTC When the current time is before the expiration Then the link is accessible and bookable When the current time is at or after the expiration Then the link displays an "expired" state and blocks booking attempts And analytics log expired link views And links configured to never expire remain bookable indefinitely
Link Revocation Behavior
Given an active smart link When the owner revokes the link Then subsequent link opens within 60 seconds return a "revoked" state and block booking And existing confirmed bookings remain intact And generating a new link does not re-activate the revoked link URL
Kit Attribution on Bookings and Analytics
Given a booking created via a smart link derived from a Starter Kit When the booking is confirmed Then the event metadata includes the originating kit ID and kit name And internal analytics attribute view, availability loaded, and booked events to that kit And exporting or querying bookings by kit returns this booking
Adoption & Effectiveness Analytics
"As an operations manager, I want to see how starter kits affect scheduling outcomes so that I can refine our presets and prove their value."
Description

Instrument events and build dashboards to measure starter kit adoption and outcomes: time-to-first-booking after signup, reduction in after-hours invites, reschedule rates, meeting success rates, and regional distribution of booked times. Provide kit-level and template-level metrics, trend lines, and CSV export. Ensure privacy-safe aggregation and configurable retention. Surface data-driven recommendations to refine kits (e.g., suggest widening windows where utilization is low). Expected outcome: visibility into business impact and continuous improvement of presets.

Acceptance Criteria
Event Tracking & Adoption Funnel Coverage
Given a new user signs up and views the Starter Kits gallery, When the gallery loads, Then an event kit_impression is recorded with properties: org_id, user_id, kit_id, ts (ISO 8601), source (web/app), session_id. Given the user selects a Starter Kit, When selection occurs, Then an event kit_selected is recorded with org_id, user_id, kit_id, ts, and idempotency_key to prevent duplicates. Given the user applies a template from a Starter Kit, When the template is applied to a workspace, Then an event template_applied is recorded with template_id, kit_id, org_id, user_id, ts. Given the user paints acceptable hours using the preset, When they save changes, Then an event timeline_painted is recorded with kit_id, template_id, org_id, user_id, ts, and hours_painted count. Given the user shares a smart scheduling link from the kit/template, When the link is created or shared, Then an event smart_link_shared is recorded with link_id, kit_id, template_id, org_id, user_id, ts, channel. Given a recipient books a meeting via the shared link, When the booking is confirmed, Then an event meeting_booked is recorded with booking_id, kit_id, template_id, org_id, booked_ts, attendee_count, regions, is_after_hours_any_attendee (boolean), and reschedule_token. Given transient network failures occur during event submission, When retries are attempted, Then events are delivered at-least-once and deduplicated by idempotency_key within 5 minutes of occurrence. Given privacy requirements, When events are stored, Then no PII (names, emails) is persisted in event properties; only hashed identifiers are stored.
Time-to-First-Booking Metric Computation
Given an organization completes signup and selects any Starter Kit, When the organization receives its first meeting_booked event associated to any kit/template within 30 days, Then time_to_first_booking is computed as booked_ts minus org_signup_ts to the nearest minute. Given internal/test traffic, When environment=test or user_is_internal=true on events, Then those events are excluded from the metric. Given product analytics, When viewing dashboards, Then the metric is displayed by kit, by template, and by org with selectable ranges (Last 7/30/90 days) and aggregations (median, P75, P90). Given data latency SLAs, When events are ingested, Then dashboards reflect new values within 10 minutes. Given insufficient data, When an org has no qualifying booking within the window, Then the metric shows N/A and is excluded from aggregates unless sample_size>=5.
After-Hours Invite Reduction Metric
Given an organization has historical booking data, When a Starter Kit is enabled for that org, Then baseline_after_hours_rate is computed from the 30 days prior to enablement and current_after_hours_rate from the selected post-enable period. Given multi-attendee meetings, When determining after-hours, Then a meeting is flagged after-hours if any required attendee's local start time falls outside their configured working hours excluding holidays and focus blocks. Given the comparison period is selected, When computing reduction, Then reduction_pct = (baseline_after_hours_rate - current_after_hours_rate) / baseline_after_hours_rate and is shown only when baseline_sample_size>=30 and current_sample_size>=30. Given insufficient baseline, When baseline_sample_size<30 or no prior data, Then the UI displays "insufficient baseline" and does not compute reduction. Given privacy constraints, When segmenting by kit/template/region, Then segments with n<5 bookings are suppressed.
Reschedule and Meeting Success Rates
Given scheduled meetings originating from Starter Kit templates, When a meeting is canceled and rebooked with the same link_id or reschedule_token within 14 days, Then it is counted as rescheduled; reschedule_rate = rescheduled_meetings / scheduled_meetings. Given meetings that occur, When computing success, Then a meeting is successful if status=completed, actual_duration >= 50% of scheduled_duration, and attended_by>=2; success_rate = successful_meetings / meetings_started. Given data quality rules, When a meeting has conflicting statuses or missing durations, Then it is excluded from both numerators and denominators and surfaced in a data_quality_exclusions count. Given analytics views, When filtering by kit or template and by date range, Then reschedule_rate and success_rate update accordingly within 10 minutes and include sample sizes. Given small samples, When sample_size<20 for a rate, Then the UI displays the rate with a low-sample warning and hides trend lines.
Regional Distribution Dashboard & Trend Lines
Given booked meetings from Starter Kits, When computing regional distribution, Then attendee-local start times are bucketed by hour-of-day and aggregated by region (Americas, EMEA, APAC) derived from attendee timezone or country mapping. Given multiple attendees in multiple regions, When attributing a meeting, Then the meeting contributes to all represented regions with weight 1/regions_count unless a host-region filter is applied. Given the dashboard time range is selected, When rendering charts, Then a heatmap of bookings by local hour and a week-over-week trend line per region are displayed, normalized per 100 bookings. Given drill-down interaction, When a user clicks a region or hour bucket, Then results filter to that selection and can be further segmented by kit or template. Given data freshness targets, When events arrive, Then the dashboard updates within 10 minutes and indicates last_refreshed_ts.
Privacy-Safe Aggregation, Retention & Export
Given organization privacy settings, When retention is configured to 3/6/12/24 months, Then raw event data older than the selected retention is permanently deleted by a daily job and excluded from all metrics; deletion completes within 7 days of eligibility. Given aggregation rules, When rendering any metric or segment, Then results for cohorts with n<5 are suppressed (k>=5), and only hashed identifiers are used internally; no emails or names are stored or exported. Given a user requests CSV export of analytics, When an export is initiated for a selected date range and segmentation, Then a CSV is generated with headers (metric_name, kit_id, template_id, period_start, period_end, value, sample_size, region) and contains only aggregated data, never raw PII. Given export delivery, When the CSV is ready, Then it is available via a secure HTTPS link expiring in 7 days and optionally emailed; for datasets up to 100k rows the export completes within 2 minutes. Given right-to-erasure, When an org requests deletion, Then all retained events and derived identifiers for that org are deleted and excluded from future recomputations within 7 days.
Recommendation Engine for Kit Refinement
Given utilization analytics, When a template's slot_utilization = booked_minutes/available_minutes < 20% for 3 consecutive weeks with available_slots>=50 per week, Then a recommendation is generated to widen or shift windows toward adjacent hours with highest historical booking probability. Given after-hours outcomes, When current_after_hours_rate > 10% with sample_size>=50, Then a recommendation is generated to tighten windows during offending hours and to add focus blocks accordingly. Given recommendation surfacing, When a user views the Starter Kit analytics page, Then recommendations are listed with predicted_impact (e.g., +X% utilization, -Y% after-hours), evidence (sample_size, timeframe), and actions Accept, Schedule A/B, or Dismiss. Given actioning a recommendation, When Accept is clicked, Then a new template version is created and versioned; When Schedule A/B is selected, Then 50% of future links use the variant until significance or max duration is reached. Given telemetry, When recommendations are shown/accepted/dismissed, Then events recommendation_shown, recommendation_accepted, and recommendation_dismissed are recorded with kit_id, template_id, org_id, ts.
Onboarding Recommendations & Guided Setup
"As a new user, I want Timeglue to recommend the right starter kit based on my team’s makeup so that I can get set up in minutes."
Description

Create a first-run flow that recommends starter kits based on connected calendar signals, declared roles, industries, geo distribution of teammates, and working hours. Provide a short guided setup that previews the recommended kit, explains its guardrails, and confirms any sensitive defaults (e.g., no-meeting days). Include A/B testing hooks for recommendation strategies and track conversion to kit adoption. Provide fallbacks when signals are sparse. Expected outcome: users get to a battle-tested setup within minutes, increasing activation and reducing churn.

Acceptance Criteria
Recommend Starter Kits From Signals (First Run)
- Given a new user has connected a calendar and provided role, industry, teammate time zones, and working hours, When the recommendation step loads, Then display 1–3 ranked starter kits relevant to the signals with a "Why this" explanation citing at least two signals per kit. - Given identical input signals, When the recommendation step is reloaded in the same session, Then the same ranked list and order are returned. - Given typical network conditions, When recommendations are requested, Then the list renders within 2 seconds at P95. - Given no eligible kit matches detected signals, When the step loads, Then automatically route the user to the fallback flow without error.
Sparse Signals Fallback Flow
- Given the user has not connected a calendar and has not provided role/industry, When the recommendation step loads, Then present a fallback wizard requesting minimal inputs (role required; industry optional) and provide a "Browse all kits" option. - Given the user declines calendar connection, When proceeding in the fallback wizard, Then the flow is not blocked and default assumptions are clearly indicated in the preview. - Given the user completes the fallback wizard, When recommendations are generated, Then at least one starter kit is presented and selectable. - Given the user selects "Browse all kits", When viewing the catalog, Then all available kits are discoverable and filterable by role and industry.
Guided Setup Preview & Guardrail Confirmation
- Given a recommended kit is selected, When the guided setup opens, Then show a timeline preview with pre-painted safe windows adjusted to the user's team time zones and declared working hours. - Then list guardrails including no-meeting days, focus-block defaults, and after-hours prevention with current values visible. - Then require explicit confirmation toggles for sensitive defaults (e.g., no-meeting days, after-hours prevention); the "Start with this kit" button remains disabled until all required confirmations are made. - Given a guardrail value is changed, When saving, Then the preview updates within 500 ms and the summary reflects the final values. - Given setup is confirmed, When submitted, Then the kit configuration is created and applied to the user's workspace without error.
A/B Strategy Assignment & Exposure Tracking
- Given an eligible first-run user opens the recommendation step, When the screen initializes, Then assign the user to a single experiment variant (e.g., strategy_A or strategy_B) deterministically and persist the assignment for 30 days or until experiment end. - Then emit an analytics event "ab_assignment" before recommendations render including user_id, experiment_key, variant, timestamp, and app_version with at least 99% delivery within 10 seconds P95; retry on failure for up to 24 hours. - Given the user returns within the assignment window, When reopening onboarding, Then the same variant is used. - Given a query parameter abVariant is provided in a non-production environment, When present, Then it overrides the variant and marks analytics events with test=true.
Conversion Tracking: Kit Adoption
- Given a user completes guided setup and applies a starter kit, When the kit is created and activated, Then emit "kit_adopted" with properties {user_id, kit_id, kit_name, variant, time_to_adoption_seconds, signals_used, session_id} and mark onboarding_state=completed. - Given the user views a recommendation, previews a kit, and confirms guardrails, When each step occurs, Then emit funnel events {recommendation_viewed, kit_previewed, guardrails_confirmed} with consistent user_id and session_id for attribution to any assigned variant. - Given network connectivity, When events are emitted, Then at least 99% of "kit_adopted" events reach the analytics backend within 10 seconds P95; if offline, events queue and flush within 24 hours of next app launch. - Given the user exits before adoption, When 30 minutes of inactivity pass or the session ends, Then emit "onboarding_abandoned" with last_step.
Preview Honors Work Hours, Holidays, and Focus Blocks
- Given connected calendars, declared working hours, team holidays, and focus blocks exist, When generating the kit preview, Then no proposed meeting window violates any participant's working hours or listed holidays, and windows do not overlap focus blocks. - Given conflicts eliminate all windows for a given day, When rendering the preview, Then the day is marked unavailable with an explanation tooltip stating the blocking guardrail(s). - Given the user toggles no-meeting days, When toggled, Then the preview hides those days immediately and the summary reflects the change. - Given teammates span multiple time zones, When generating the preview, Then all proposed windows are displayed in local time for the viewer with clear cross-zone labels and do not schedule outside any participant's working hours. - Given the user switches to a different recommended kit, When selected, Then the preview recalculates using the same guardrail inputs in under 1 second P95.

Risk Precheck

Before you invite anyone, a fast simulation surfaces after‑hours exposure, holiday conflicts, and compliance risks across your imported teams. Get one-click fixes and publish a clean plan on day one.

Requirements

Instant Risk Simulation
"As a remote team lead, I want to see instant risk indicators while adjusting meeting windows so that I can avoid after-hours and holiday conflicts without trial and error."
Description

Compute after-hours exposure, holiday conflicts, focus-block collisions, and regional compliance risks in real time as the host paints or drags meeting windows on the Timeglue timeline. The simulation ingests each participant’s time zone, working hours, OOO and holiday calendars, and configured policy rules to produce a normalized risk score and a breakdown of violations per slot. Results update instantly with p95 latency under 300 ms for up to 100 participants across an 8‑week horizon, using incremental recomputation and caching; for larger cohorts the UI shows progressive results within 2 seconds. Output includes slot-level severity, per-person flags, and machine-readable codes consumed by recommendations, UI overlays, and reporting. The engine runs automatically before link creation or invite send, ensuring issues are caught early and without manual calculation.

Acceptance Criteria
Live Drag Update ≤300 ms for ≤100 Participants
Given a timeline with up to 100 participants over an 8‑week horizon and all required calendars and policies loaded When the host drags or paints to create, resize, or move a single time slot Then the risk simulation recomputes and the UI overlay updates with the new results with p95 end‑to‑end latency ≤300 ms measured over at least 200 interactions on standard target hardware Given the host continuously drags a slot across the timeline When simulation update intervals are sampled over the drag path Then 95% of successive updates maintain ≤300 ms p95 latency and no single update exceeds 1,000 ms
Progressive Results within 2 Seconds for >100 Participants
Given a configuration with more than 100 and up to 1,000 participants and an 8‑week horizon When the host modifies a slot Then the first partial simulation result (aggregate slot severity and preliminary risk score) appears within 2,000 ms p95, and per‑person flags stream in progressively thereafter Given progressive rendering is active When the final computation completes Then the progressive result converges exactly to the final result with no discrepancy in risk score or flags
Violation Coverage: After‑Hours, Holiday, Focus‑Block, Compliance
Given participants with time zones, working hours, OOO and holiday calendars, focus blocks, and policy rules When the simulation runs for any slot Then it detects and reports all applicable violations across after‑hours exposure, holiday conflicts, focus‑block collisions, and regional compliance risks, including zero counts when none apply Given policy thresholds are configured for severity When violations are detected Then each violation is assigned a severity level per policy and contributes to the slot breakdown
Normalized Risk Score and Slot‑Level Breakdown
Given a slot with detected violations and policy weights When the simulation completes Then a single numeric normalized risk score is emitted for the slot and equals the policy‑weighted sum of violation components within a tolerance of ±0.001 compared to an independent recomputation Given a slot has no violations When the simulation completes Then the normalized risk score equals the configured minimum value and the breakdown shows zero counts across all categories
Per‑Person Flags and Machine‑Readable Codes
Given the simulation identifies violations When results are produced Then each affected participant has an associated per‑person flag that includes participant identifier, violation type, severity, and a machine‑readable code from the allowed set defined in policy Given the result payload is consumed by internal modules When passed to the recommendations, UI overlay, and reporting consumers in a test harness Then each consumer accepts the payload and processes codes without error (HTTP 200/OK or equivalent success signal)
Automatic Pre‑Invite/Pre‑Link Risk Check
Given the host attempts to create a shareable link or send invites When the action is initiated Then the risk simulation runs automatically before completion and results are presented to the host within the same interaction flow Given the simulation detects any violation at or above the configured alert threshold When the host proceeds Then the UI displays the issues inline and offers the option to continue or adjust the plan per policy settings
Incremental Recomputation and Caching
Given a session where the host makes successive edits to overlapping time slots When only a subset of the previously computed horizon changes Then the engine recomputes only the slots that intersect the changed interval and reuses cached results for unaffected slots, as evidenced by debug metrics showing zero recomputations outside the changed set Given the host revisits a previously computed, unchanged slot within the same session When the result is requested again Then the engine serves the cached result with no recomputation and latency ≤100 ms p50
Compliance & Policy Rules Engine
"As a workspace admin, I want to define and enforce scheduling policies across regions so that meetings consistently comply with local labor rules and company guidelines."
Description

Provide a configurable rules engine that encodes quiet hours, minimum rest periods, maximum after-hours frequency, weekend restrictions, protected holidays, and regional labor constraints (e.g., EU Working Time Directive–style limits). Policies are versioned and assignable at workspace and team levels, with rule packs auto-applied per participant based on locale and employment region. The engine evaluates rules deterministically and outputs standardized violation objects with severity, rationale, and references to the triggering policy. Administrators can customize thresholds and add exceptions with expiry dates, enabling nuanced governance without hardcoding. Integrates tightly with the simulation, recommendations, guardrails, and reporting so that all risk signals and actions are policy-driven and auditable.

Acceptance Criteria
Deterministic Evaluation with Standardized Violation Output
Given a fixed set of participants, calendars, time zones, and an identical policy_version_id And the same evaluation window and engine configuration When the rules engine is executed multiple times Then the resulting violation list is identical in content and order across runs And each violation includes: violation_id (UUID), rule_key, severity in {Low, Medium, High, Critical}, rationale (non-empty string), policy_id, policy_version_id, participant_id(s), time_window, and reference_ids (e.g., event_id or holiday_id) And the engine returns an evaluation_run_id and generated_at timestamp in ISO 8601 UTC And the response conforms to the published JSON schema and passes validation
Policy Versioning and Assignment at Workspace and Team Levels
Given a workspace with Policy A v1 assigned at the workspace level And Team X explicitly assigned Policy A v1, and Team Y inherits from workspace When an admin publishes Policy A v2 and assigns it to the workspace default Then evaluations for Team X continue to use Policy A v1 until Team X is reassigned And evaluations for Team Y use Policy A v2 immediately And each evaluation record stores the policy_version_id used and remains immutable after creation And team-level assignment takes precedence over workspace-level defaults
Auto-Apply Rule Packs by Locale and Employment Region
Given participants with employment_region EU-DE and locale de-DE And participants with employment_region US-CA (non-exempt) and locale en-US And a participant with unknown employment_region When the rules engine evaluates Then EU participants automatically receive the EU Working Time Directive rule pack And US non-exempt participants receive the US Overtime/Rest rule pack And participants with unknown employment_region receive the workspace default rule pack And when a participant’s employment_region changes with an effective_date, subsequent evaluations use the new region’s rule pack from that date And locale does not override employment_region when both are present
Configurable Thresholds and Time-Bound Exceptions
Given an admin updates quiet_hours to 19:00–08:00 local, min_rest_hours to 11, max_after_hours_per_week to 2, and adds weekend_restricted = true in Policy A v2 And creates an exception for participant P to ignore quiet_hours for a product launch from 2025-09-10T18:00Z to 2025-09-12T06:00Z with reason and approver When the engine evaluates during the exception window Then violations for quiet_hours for participant P are suppressed but other rules still apply And after the exception end, evaluations revert to the baseline policy automatically And all exceptions include: scope (participant/team/workspace), rule_keys, start_at, end_at, created_by, reason, approver, and are recorded in the audit log And threshold changes take effect only for evaluations referencing the updated policy_version_id
Core Rule Types: Quiet Hours, Rest Periods, After-Hours Frequency, Weekends, Holidays
Given a schedule proposal that places meetings at 07:30 local, 20:30 local, on Saturday, and the day of a protected holiday And the participant worked until 23:30 the night before and has two after-hours meetings already this week And the policy defines quiet_hours 19:00–08:00, min_rest_hours 11, max_after_hours_per_week 2, weekend_restricted true, and a holiday list When the rules engine evaluates the proposal Then it flags violations for quiet_hours (07:30 and 20:30), min_rest_hours (due to 23:30 previous shift), weekend_restricted (Saturday), holiday_protected (holiday day), and after_hours_frequency (exceeding 2/week) And each violation includes a severity computed per policy thresholds and a rationale referencing the specific rule and time window
Risk Precheck Simulation Integration and One-Click Fixes
Given a user runs Risk Precheck for a proposed 2-hour meeting window across 8 participants in 5 regions When the rules engine evaluates the window Then the precheck summary displays total violations by severity, count of affected participants, and top 3 blocking policies with links to details And a one-click Fix option proposes at least one alternative window within the next 7 days that reduces Critical and High violations to zero while keeping attendee count unchanged And the simulation completes within 2 seconds for up to 10 participants and within 5 seconds for up to 50 participants
Guardrails and Reporting with Auditability
Given guardrail mode is set to Block for severity >= High and Warn for Medium When a user attempts to publish invites that would trigger High or Critical violations Then the system blocks the action with a policy-driven message listing violated rules and references And when violations are Medium only, the system requires an explicit override with reason and persists the override decision And reporting exports include evaluation_run_id, policy_id, policy_version_id, participant_id, rule_key, severity, rationale, time_window, and any exceptions applied And an audit trail records actor_id, action, timestamp, policy_version_id, and diff of changes for policy edits and exceptions
One‑Click Risk Fixes
"As a planner, I want one-click fixes that resolve conflicts the moment they’re detected so that I can publish a compliant schedule without manual tweaking."
Description

Offer actionable, single-click recommendations that automatically adjust proposed meetings to minimize risk while honoring host constraints. Fixes include shifting the window by the lowest-cost offset, rotating times across weeks for fairness, excluding conflicting holidays, shortening duration to fit within safe hours, proposing split sessions by region, and converting some attendees to async or optional when appropriate. Each fix presents a preview of the change and its impact on risk score, with clear explanations and undo support. Applying a fix updates the timeline, re-runs the simulation, and logs the decision for compliance reporting. The system prioritizes fixes by expected risk reduction and preserves previously accepted exceptions.

Acceptance Criteria
Lowest-Cost Offset Shift
Given a proposed meeting incurs after-hours exposure or compliance flags for one or more attendees and host constraints are defined When the user selects the "Shift window" one-click fix Then the system evaluates all viable start-time offsets at the scheduling granularity within host constraints and selects the offset with the lowest total risk score; ties are broken by smallest absolute shift and then earliest viable start time And a preview shows the proposed new window, impacted attendees, and the risk score delta with a plain-language explanation And upon Apply, the timeline updates to the selected window, previously accepted exceptions are preserved, the simulation re-runs, the updated risk score is displayed, a compliance log entry is recorded with before/after details and approver, and Undo is enabled to revert in one click And the apply operation completes within 2 seconds for up to 100 attendees across up to 10 time zones
Holiday Exclusion Adjustment
Given one or more proposed occurrences overlap with imported regional holidays for any attendee group When the user selects the "Exclude conflicting holidays" one-click fix Then only the conflicting occurrences are removed or moved to the nearest non-holiday date that minimizes risk and honors host constraints and cadence rules And a preview lists the adjusted dates, affected regions, and the risk score delta with explanation And upon Apply, the series is updated accordingly, previously accepted exceptions are preserved, the simulation re-runs, the risk score is refreshed, a compliance log entry is recorded, and Undo is enabled
Duration Shortening to Fit Safe Hours
Given the proposed meeting duration causes any required attendee to fall outside safe hours When the user selects the "Shorten duration" one-click fix Then the system proposes the minimal reduction, in configured increments, needed to bring all required attendees within safe hours without going below the host’s minimum duration And a preview shows the new duration, which risks are eliminated, and the risk score delta And upon Apply, the timeline reflects the new duration, previously accepted exceptions are preserved, the simulation re-runs, the updated risk score is displayed, a compliance log entry is recorded, and Undo is enabled
Regional Split Session Proposal
Given attendees span regions such that no single time window satisfies safe hours for all required attendees When the user selects the "Split by region" one-click fix Then the system clusters attendees into the minimal number of sessions so each session fits within its cluster’s safe hours and host constraints And mandatory roles/functions are present in each session; optional/async attendees may be assigned per session or as async And a preview lists each proposed session with participants, local times, and per-session and overall risk score deltas And upon Apply, the sessions are created on the timeline, previously accepted exceptions are preserved, the simulation re-runs, the risk score is refreshed, a compliance log entry (including session mappings) is recorded, and Undo is enabled
Weekly Time Rotation for Fairness
Given a recurring weekly meeting includes attendees from multiple regions When the user selects the "Rotate times" one-click fix Then the system generates a rotation across at least four consecutive weeks that reduces the max–min spread of attendees’ local start times by at least 50% versus baseline while honoring host constraints And a preview displays the rotated schedule with per-week local times and the projected risk score trend And upon Apply, the series is updated, previously accepted exceptions are preserved, the simulation re-runs, the updated risk score is displayed, a compliance log entry is recorded, and Undo is enabled
Convert Attendees to Async or Optional
Given some attendees are eligible for async participation or marked optional by role rules When the user selects the "Convert to async/optional" one-click fix Then the system proposes converting the minimal set of attendees needed to achieve safe-hours compliance without converting required attendees or removing representation of any required function And converted attendees are excluded from scheduling constraints and marked optional or async for invitations And a preview lists who will be converted with rationale and the risk score delta And upon Apply, attendee roles are updated, previously accepted exceptions are preserved, the simulation re-runs, the risk score is refreshed, a compliance log entry (including conversion details) is recorded, and Undo is enabled
Fix Prioritization, Preview, Re-simulation, Undo, and Logging
Given multiple one-click fixes are available for a meeting or series When the system presents recommendations Then fixes are ordered by expected risk reduction (descending); ties are broken by lowest disruption (smallest time shift, no increase in session count, least duration change) and then earliest completion time And each recommendation includes a preview showing the precise change and expected risk score delta with a plain-language explanation And when any fix is applied, the timeline updates immediately, the simulation re-runs, the risk score refreshes, previously accepted exceptions are preserved, a compliance log entry with before/after snapshot and approver is recorded, and Undo can revert the change and its log entry And the recommendation list renders within 1.5 seconds for up to 100 attendees across up to 10 time zones
Risk Heatmap Timeline
"As a meeting organizer, I want a visual heatmap of risk across the timeline so that I can quickly spot safe windows and understand who is impacted."
Description

Render a color-coded heatmap overlay on the draggable timeline to visualize aggregate risk intensity across proposed windows, with accessible contrast, keyboard navigation, and responsive layouts. Users can drill into any slot to view per-person and per-region risk details, filter by risk type (after-hours, holiday, policy), and see explanations tied to specific rules. The heatmap updates in real time with the simulation and supports grouping participants (e.g., EMEA, APAC, Engineering) to identify hotspots quickly. Critical violations trigger inline callouts and affordances to apply relevant one-click fixes. The visualization is optimized to remain smooth at 60 fps for typical cohorts and gracefully degrade to batched updates for very large teams.

Acceptance Criteria
Real-time Heatmap Updates at 60 FPS (Typical Cohorts)
Given a schedule with up to 80 participants across up to 10 groups in a 4-week timeline When the user modifies constraints (paints acceptable hours, adds/removes participants, toggles holidays, edits focus blocks) Then the heatmap overlay updates visibly within 150 ms and sustains an average frame time ≤ 16.7 ms with p95 ≤ 24 ms during the update sequence And the update is diffed (no full-canvas flicker), legend and risk totals remain consistent with the visible state And no input events are dropped and drag/scroll latency remains ≤ 50 ms during updates
Drilldown Panel Shows Per-Person/Region Risk With Explanations
Given focus is on a heatmap cell representing a time slot When the user clicks or presses Enter/Space Then a drilldown opens within 300 ms showing per-region sections with counts, per-person entries with risk type badges (after-hours, holiday, policy), and rule-based explanations And items are sorted by severity descending, then by name; links to underlying policy/holiday sources are available And an empty state “No risks for this slot” appears when applicable; Esc, outside click, or Close button dismisses and returns focus to the originating cell
Risk Type Filtering Updates Heatmap and Legend
Given filter controls for After-hours, Holiday, and Policy risks When the user toggles any combination of filters Then the heatmap recomputes intensities and the legend updates to reflect only selected risk types within 200 ms And the filter state persists for the session and is encoded in shared links And if all filters are off, a zero-state message appears and the heatmap shows no risk intensity; all filter controls are operable via keyboard
Participant Grouping Highlights Hotspots
Given predefined or user-defined groups (e.g., EMEA, APAC, Engineering) When grouping is enabled or group visibility is toggled Then the heatmap intensity per slot reflects aggregated weighted risk across visible groups using the documented formula, and top 10% intensity hotspots are marked And toggling group visibility re-renders within 200 ms and updates counts and legend And group order is applied consistently in the drilldown sections and persists for the session
Accessibility and Responsive Navigation Compliance
Rule: Heatmap colors meet WCAG 2.2 AA; text/icon contrast ≥ 4.5:1; non-text graphical indicators ≥ 3:1; color is not the sole indicator (pattern/shape/tooltip provided) Rule: Keyboard operation covers timeline, cells, filters, and drilldown; Arrow keys move cell focus; Tab/Shift+Tab follow logical order; Enter/Space activate; Esc closes; visible focus ring ≥ 2 px with 3:1 contrast Rule: Each cell has an accessible name including local time range, aggregated risk score (0–100), and top risk type; screen reader announcements update within 250 ms; no critical axe violations Rule: Responsive layouts at 320–480 (mobile), 481–1024 (tablet), and ≥1025 (desktop) preserve readability and function; horizontal scroll on mobile; filters collapse to sheet; touch targets ≥ 44×44 dp; no horizontal overflow
Critical Violations Callouts and One-Click Fixes
Given any slot contains a critical violation per policy When that slot is visible on the timeline Then an inline callout appears within 100 ms summarizing the violation and offering one-click fixes (shift to nearest compliant window, exclude participant, move to alternative date) And invoking a fix updates the plan and heatmap within 300 ms, clears the related critical flag, and provides an undo option (on-screen and Ctrl/Cmd+Z) for 10 s And if multiple criticals exist, they are prioritized by severity; fixes that would introduce a new critical are disabled with explanatory tooltip
Graceful Degradation for Very Large Teams
Given a cohort exceeding 200 participants or 20 groups When simulation updates occur Then the renderer switches to batched tile updates with lightweight placeholders and maintains interactive latency ≤ 100 ms, showing progress indicators during recompute And frame rate does not drop below 30 fps during recomputation bursts and returns to ≥ 60 fps after completion And the final visual state matches the non-batched result with ≤ 1% difference in aggregated risk score per slot
Smart Link Guardrails
"As a team lead, I want scheduling links that only allow compliant times so that no one can accidentally book meetings that violate work hours or holidays."
Description

When generating shareable scheduling links, automatically restrict bookable times to low-risk windows based on the latest simulation and policies, preventing after-hours and holiday bookings by default. At booking time, guardrails revalidate slots using the invitee’s locale and assigned policies, blocking or warning if new conflicts arise and suggesting compliant alternatives. Holds placed on calendars reflect sanitized availability, and ICS invites include compliance notes for transparency. The guardrails expose configuration flags (strict block vs. warn) and webhooks for external workflows. This ensures the published link cannot be used to bypass policy or create after-the-fact conflicts.

Acceptance Criteria
Link Generation Applies Latest Low-Risk Windows
Given organization policies P and a completed simulation S ≤ 15 minutes old for selected teams When an organizer generates a smart scheduling link Then the link’s available slots exactly equal S.lowRiskWindows And no slot overlaps policy-defined after-hours, holidays, or focus blocks And the link payload includes simulationVersionHash = S.hash and policyVersionIds = P.ids
Booking-Time Revalidation With Invitee Locale and Policies
Given an invitee locale L and assigned policies Pi are available at booking When the invitee submits a booking request for slot S at time T Then S is revalidated against organizer policies Po, team policies Pt, and Pi using timezone L and UTC rules (including DST) And if strictMode = true and any violation exists, booking is blocked with HTTP 403 and UI message "Slot violates policy" within 1500 ms p95 And if strictMode = false and any violation exists, booking returns HTTP 409 with a warning and at least 3 compliant alternative slots within ±7 days of T And if no violation exists, booking succeeds with HTTP 201 and the event is created
Guardrails Configuration Flags: Strict vs Warn
Given a smart link is being created When strictMode is not explicitly set Then strictMode defaults to true When strictMode is set to false at link creation Then the setting persists on the link metadata and is enforced at booking time When strictMode is toggled after the link is shared Then subsequent bookings reflect the new value within 60 seconds And analytics events guardrails.mode_changed and guardrails.violation_blocked|warned are emitted with linkId, policyIds, and timestamp
Sanitized Calendar Holds Reflect Guardrailed Availability
Given calendar hold placement is enabled When a visitor selects a tentative slot S that passes guardrails Then a hold is placed on the organizer’s calendar with title "[Hold] Timeglue: {Invitee or Anonymous}", start/end = S, and description including linkId and simulationVersionHash When S is invalidated by a policy update or revalidation failure Then the hold is removed within 60 seconds and an audit log entry is recorded Then no holds are created for slots that fail guardrails
ICS Invites Carry Compliance Notes
Given a booking succeeds When the ICS file is generated and sent Then the DESCRIPTION includes a compliance note with policy IDs, rationale summary, and simulationVersionHash And custom properties X-TIMEGLUE-POLICY-IDS and X-TIMEGLUE-SIM-VERSION are present And the ICS validates against RFC 5545 and imports without errors in Google Calendar, Outlook, and Apple Calendar And the event timezone matches the organizer’s selection and preserves start/end when viewed in the invitee’s timezone
Webhooks for Guardrail Events Are Emitted Reliably
Given a webhook endpoint is configured and enabled When a guardrail is triggered (blocked or warned) or a booking is created Then a POST is delivered within 2 seconds p95 with eventType ∈ {guardrail.blocked, guardrail.warned, booking.created} And the payload includes eventId, linkId, policyIds, simulationVersionHash, idempotencyKey, and an HMAC-SHA256 signature header And on 2xx, no retries occur; on 4xx, retries stop; on 5xx, retries use exponential backoff with jitter for up to 24 hours And events for the same link are delivered in timestamp order and are idempotent by idempotencyKey
Policy Bypass Prevention and Direct URL Hardening
Given a client attempts to book a slot not in the current lowRiskWindows or tampers with link parameters When the booking request is submitted Then the server rejects with HTTP 403 (strict) or HTTP 409 with compliant alternatives (warn) And link parameters are signed; on signature mismatch the request is rejected with HTTP 401 without revealing policy details And if the cached simulation is older than 15 minutes, it is refreshed before finalizing booking and the latest policies are enforced And during DST transitions, nonexistent or repeated times are excluded at link generation and revalidation
Compliance Report & Audit Trail
"As an operations manager, I want an auditable compliance report of the scheduling plan so that I can demonstrate policy adherence and understand any accepted risks."
Description

Generate a shareable report that summarizes detected risks, applied fixes, accepted exceptions, and final compliance status per policy and participant for each scheduling plan. Reports can be exported to PDF and CSV, include timestamps, decision rationales, and policy versions, and are stored in an immutable audit log with user attribution. Privacy settings control redaction of personal details while preserving necessary compliance evidence. The audit trail supports search and filtering by team, date range, and policy, enabling HR or legal teams to verify due diligence. Integrates with workspace retention policies and allows secure, expiring share links for stakeholders.

Acceptance Criteria
Generate Comprehensive Compliance Report per Scheduling Plan
Given a scheduling plan with N participants, M detected risks, F applied fixes, and E accepted exceptions across policies When a user with Reporter permissions generates the compliance report for the plan Then the report contains per-participant and per-policy final compliance status And the report lists exactly M risks, F fixes, and E exceptions with stable IDs referencing source records And totals and per-policy counts match the Risk Precheck results 1:1 And report generation completes within 5 seconds for plans up to 200 participants
Export Reports to PDF and CSV with Fidelity
Given a generated compliance report When the user exports the report as PDF Then the PDF contains the same content and section order as the in-app report, is text-searchable, and is generated within 5 seconds And the PDF metadata includes report ID, plan ID, generation timestamp (UTC ISO 8601), and policy version(s) When the user exports the report as CSV Then the CSV uses UTF-8 encoding with a header row and RFC 4180-compliant quoting And the CSV includes one row per participant-policy decision with columns: report_id, plan_id, participant_id, policy_id, status, decision_type, rationale, timestamp_utc, actor_user_id And both exports reproduce redaction settings applied at export time
Capture Timestamps, Decision Rationales, and Policy Versions
Given risks, fixes, and exceptions are recorded for a plan When the compliance report is generated Then each risk, fix, and exception entry includes a non-empty UTC ISO 8601 timestamp, the policy ID and policy version, and the actor user ID And each fix and exception includes a non-empty decision rationale field And report generation fails with a clear validation error if any required field is missing
Immutable Audit Log with User Attribution
Given a compliance report is published Then an audit log entry is appended with fields: report_id, plan_id, actor_user_id, actor_role, action, timestamp_utc, policy_version(s), content_hash, prev_hash And the audit log is append-only; attempts to modify or delete an entry return HTTP 403 and append an attempted_mutation event And the audit chain hash verification API returns valid for the report’s history
Privacy Redaction Controls Applied to Reports and Exports
Given workspace privacy settings specify redaction level and a user enables redaction for a report or share When viewing or exporting the report Then personal identifiers (name, email, phone, calendar event titles) are masked, and participant IDs are pseudonymized while policy IDs, timestamps, and statuses remain visible And the redaction state is displayed to viewers and recorded in the audit log with actor_user_id and timestamp_utc And only users with Compliance Admin role can view or export the unredacted version
Search and Filter Audit Trail by Team, Date Range, and Policy
Given audit log entries exist across multiple teams, dates, and policies When a user filters by team(s), date range, and policy ID(s) Then only matching entries are returned, with accurate counts and pagination And the first page response returns within 2 seconds for up to 10,000 matching entries And search results are stable and repeatable given the same filters and sort
Secure, Expiring Share Links and Retention Integration
Given a compliance report exists When a user creates a share link with an expiry timestamp Then the link URL contains a token with at least 128 bits of entropy and scope limited to read-only access for that report And access via the link is logged with actor type external_link and timestamp_utc And the link becomes invalid immediately upon revocation or after expiry and returns HTTP 410 And when workspace retention deletes the underlying report after the configured period, all associated share links are invalidated and resolve to HTTP 404

Drift Guard

Watches HRIS/IdP changes during your first 30 days—new hires, team moves, location changes—and auto-updates teams, hours, and holiday sets. Sends a compact Slack/Teams digest so Zero‑Touch stays zero effort.

Requirements

Secure HRIS/IdP Connectors
"As a workspace admin, I want to connect our HRIS/IdP securely and receive normalized employee change events so that Drift Guard can keep Timeglue in sync without manual uploads."
Description

Build OAuth2/SAML-based connectors for Okta, Azure AD, Google Workspace, Workday, and BambooHR that ingest user and organization events (new hire, department/team change, location/time zone update, manager change, termination) via webhooks or scheduled polling. Implement least-privilege scopes, secrets management, and incremental sync to minimize load. Normalize payloads into a common schema for Drift Guard, deduplicate events, and backfill the initial roster on activation. Integrate with Timeglue’s identity graph to match users by email/external IDs and handle merges for duplicates.

Acceptance Criteria
Auth, Least-Privilege Scopes, and Secrets Management
- Given a connector install flow, When the admin authorizes, Then only provider-specific read-only scopes from the approved allowlist are requested and granted. - Given an access/refresh token is issued, Then tokens and client secrets are stored encrypted at rest (AES-256) in the centralized secrets manager and never logged or exposed via UI or logs. - Given a token is near expiry (<= 5 minutes), When a sync starts, Then the token is refreshed using OAuth2 refresh flow without user interaction and the new token replaces the old atomically. - Given an attempt to add an unapproved scope, Then the authorization is blocked and an admin-facing error explains required scopes. - Given a secret rotation event is triggered, Then the connector uses the newly rotated secret within 60 seconds without sync failure.
Webhook Subscription Verification and Secure Delivery
- Given a provider that supports webhooks, When a subscription is created, Then endpoint verification is completed by returning the provider's challenge within 2 seconds over TLS 1.2+. - Given a webhook is received, Then a provider-specific signature (e.g., HMAC) is validated; if invalid, a 401 is returned and the event is not processed. - Given transient handler failure, When the provider retries, Then exponential backoff is honored and the endpoint returns 2xx only after durable enqueue; otherwise returns 5xx. - Given sustained load of 100 events/second, Then the webhook handler maintains p95 latency < 500 ms with zero data loss.
Incremental Sync via Scheduled Polling
- Given a provider without reliable webhooks, When scheduled polling runs, Then only records changed since last_successful_cursor are requested using provider delta APIs; if unavailable, client-side cursors use updated_at fields. - Given pagination is required, Then all pages are retrieved until exhausted with rate limit compliance (never exceeding provider quotas). - Given a changed record is detected, Then it is delivered to the pipeline within 15 minutes of change detection. - Given a poll cycle completes, Then last_successful_cursor is persisted atomically; on failure, the previous cursor is retained to avoid gaps.
Normalize and Map Events to Common Drift Guard Schema
- Given events from any supported provider, When normalized, Then they conform to the schema: event_type in {new_hire, department_change, team_change, location_change, timezone_update, manager_change, termination}, provider, provider_user_id, external_ids[], email, effective_at (UTC ISO-8601), recorded_at (UTC ISO-8601), attributes{department, team, location, time_zone (IANA), manager_id, status}. - Given a provider omits an attribute, Then the normalized field is null and the event still validates against the schema. - Given a non-IANA timezone input, Then it is converted to a valid IANA tz or the event is rejected with an explicit error. - Given a malformed payload, Then normalization fails fast and the error is logged with correlation_id without blocking the stream.
Idempotent Deduplication of Replayed Events
- Given an event with the same idempotency key (provider + provider_event_id or deterministic hash of payload + effective_at) already processed within 30 days, Then the event is skipped with no side effects. - Given two semantically identical events with different provider_event_ids, When their normalized fingerprints match, Then only one downstream action is emitted. - Given at-least-once delivery from the provider, Then no duplicate changes are applied downstream in Drift Guard.
Identity Graph Matching and Duplicate Merge Handling
- Given an incoming user from any connector, When matching runs, Then the identity is linked to an existing person if any of the following match rules succeed: exact primary email, external_id match (oktaId/azureObjectId/googleUserId/workdayWorkerId/bambooEmployeeId), or verified secondary email. - Given two records match, Then a single canonical person is maintained, source links are stored, and attribute merges follow precedence: most recent effective_at > provider priority order (Okta, Azure AD, Google Workspace, Workday, BambooHR) > existing value. - Given match confidence is below threshold, Then no automatic merge occurs and a review task is queued with evidence. - Given a merge reversal is triggered, Then the graph splits cleanly and provenance is preserved.
Initial Backfill on Connector Activation
- Given a connector is activated, When backfill runs, Then all active users and their current attributes are fetched and normalized within 60 minutes for directories up to 10,000 users. - Given terminations within the last 30 days exist, Then terminated users are included with termination events. - Given backfill completes, Then a deduplicated snapshot is stored and incremental sync is enabled with a cursor set to completion time. - Given backfill fails mid-run, Then it can be safely resumed without reprocessing already completed pages.
Field Mapping & Change Classification
"As a workspace admin, I want to map HR attributes to Timeglue teams, hours, and holidays so that detected changes translate into the correct scheduling settings."
Description

Provide an admin UI to map HRIS/IdP attributes (department, cost center, location, country, timezone, manager, employment status) to Timeglue entities (team, location/time zone, working-hours template, holiday set). Define rules to classify changes into actionable categories: New Hire, Team Move, Location Change, Schedule Template Change, Termination. Support defaults and per-location overrides, preview of evaluated rules, and dry-run mode to verify outcomes before enabling auto-updates. Persist mapping versioning to ensure reproducibility and auditability.

Acceptance Criteria
Attribute Mapping UI with Defaults and Per-Location Overrides
Given I am an admin on the Field Mapping screen When I select a Timeglue entity (team, location/time zone, working-hours template, holiday set) Then I can map it to any HRIS/IdP attribute (department, cost center, location, country, timezone, manager, employment status) Given a global default mapping exists When I create a per-location override Then the override is saved and shown as scoped to that location Given I save the mapping configuration When I reopen the screen Then the exact mappings and overrides are loaded and displayed
Override Precedence and Inheritance
Given a global default and a country-level override for working-hours template, and a city-level override for Berlin When an employee's location is Berlin, DE Then the Berlin override is applied Given no city-level override but a country-level override exists When an employee's location is Munich, DE Then the country-level override is applied Given neither city nor country override exists When an employee's location is Toronto, CA Then the global default is applied
Change Classification Rules
Given an employee appears in HRIS with employment_status=active and no existing Timeglue profile When the rules are evaluated Then the change is classified as New Hire Given an active employee's department or cost center changes When the rules are evaluated Then the change is classified as Team Move Given an active employee's location, country, or timezone changes (post-mapping) When the rules are evaluated Then the change is classified as Location Change Given an active employee's mapped working-hours template changes due to mapping rules When the rules are evaluated Then the change is classified as Schedule Template Change Given an employee's employment_status transitions to terminated or inactive When the rules are evaluated Then the change is classified as Termination Given multiple changes occur simultaneously When the rules are evaluated Then classifications are assigned in priority order: Termination > New Hire > Location Change > Team Move > Schedule Template Change
Rule Evaluation Preview
Given a selected set of employees or a fetched HRIS diff When I click Preview Then I see for each employee: detected changes, resulting classifications, and proposed updates to team, location/time zone, working-hours template, and holiday set Given the preview is shown When I toggle filters by classification Then the list updates to only show matching employees and counts per classification are updated Given the preview is shown When I open an employee row Then I see the mapping inputs (attribute values), the matched rules/overrides, and the reason for each proposed update
Dry-Run Mode
Given Dry-Run mode is enabled When a sync is executed Then no changes are persisted to Timeglue teams, working-hours templates, holiday sets, or user records Given Dry-Run mode is enabled When a sync is executed Then a report is generated with the same contents as Preview, including counts by classification and proposed actions Given Dry-Run mode is enabled When I inspect system logs or audit entries Then entries are marked as Dry-Run and contain no mutation IDs
Mapping Versioning and Auditability
Given I save a mapping configuration When I save changes again Then a new version is created with a monotonically increasing version ID, timestamp, and editor identity Given multiple versions exist When I view the audit log for a classified change Then the mapping version used for that evaluation is shown and can be opened Given I re-run evaluation for a historical timestamp When I select mapping version V at time T Then the results are identical to the original evaluation performed with version V
Fallbacks and Error Handling
Given an HRIS attribute value is unmapped and a global default exists When rules are evaluated Then the default is applied and a warning is recorded Given an HRIS attribute value is unmapped and no default exists When rules are evaluated Then the change is marked Unclassified, no updates are proposed, and the preview highlights the missing mapping Given a mapping references a deleted Timeglue entity When I run validation Then the mapping cannot be saved and the UI identifies the invalid reference Given required mappings are incomplete When I attempt to enable auto-updates Then the system blocks enablement and lists the missing mappings
Auto-Update & Sync Engine
"As a scheduling owner, I want changes from HR to automatically update users’ teams, time zones, hours, and holidays so that after-hours invites are prevented by default."
Description

Implement an idempotent engine that applies classified changes to Timeglue: create user profiles for new hires, update team membership, assign location-derived time zones, apply working-hours templates, and attach appropriate holiday calendars. Enforce guardrails (e.g., only within activation window, ignore terminations outside scope), resolve conflicts, and queue retries on transient failures. Write structured audit logs for every mutation with before/after state, source event, and correlation IDs. Ensure updates cascade to scheduling constraints so after-hours invites are prevented without manual intervention.

Acceptance Criteria
New Hire Provisioning within Activation Window
Given a new_hire event from a connected HRIS/IdP occurs within the 30-day activation window and includes required attributes (full_name, work_email, team_id, location_code), When the engine processes the event, Then a user profile is created exactly once (idempotent on event_id and correlation_id) and assigned the specified team_id, And the user's time zone is set from the location_code mapping, And the location-derived working-hours template and holiday calendar are applied, And scheduling constraints are updated within 60 seconds to block after-hours invites, And a structured audit log entry is written with before/after state, source_event_id, correlation_id, actor="drift_guard", and timestamps, And a digest line item is queued for the next Slack/Teams digest window, And reprocessing the same event yields no duplicate records or changes (no-op with audit dedupe marker).
Team Move Updates Membership and Scheduling Constraints
Given a team_change event occurs for an existing user within the activation window, When the engine processes the event, Then the user is removed from the previous team and added to the new team atomically, And the new team's working-hours template and holiday calendar are applied, And any prior team-based scheduling constraints are replaced within 60 seconds to reflect the new team's hours, And no duplicate team memberships exist for the user, And a structured audit log captures old_team_id, new_team_id, before/after scheduling settings, and correlation_id, And reprocessing the same event or a duplicate message results in an idempotent no-op with the same final state.
Location Change Remaps Time Zone, Hours, and Holidays
Given a location_change event occurs for a user within the activation window, When processed, Then the user's time zone is updated to the new location's zone, And the location's working-hours template and holiday calendar are applied, And obsolete location-derived constraints are removed and new constraints are active within 60 seconds to prevent after-hours invites, And meetings created after this update respect the new constraints, And a structured audit log records old/new location, time zone, hours template, and holiday set with correlation_id, And duplicate or replayed events are handled idempotently with no additional side effects.
Scope Guardrails and Ignore Rules
Given an inbound event is outside the 30-day activation window, or is a termination event, or is missing mandatory attributes, When the engine evaluates the event, Then no mutations are applied to user, team, time zone, hours, holidays, or constraints, And an audit log entry is written with outcome="ignored", reason_code in {out_of_scope_window, termination_ignored, validation_failed}, event_id, and correlation_id, And the event is not included in per-user digest lines (only aggregated counts if applicable), And the engine returns a handled status without retries.
Deterministic Conflict Resolution for Concurrent Events
Given multiple events for the same user (e.g., new_hire, team_change, location_change) arrive close together or out of order, When the engine processes them, Then it orders application by effective_at timestamp, then event_time, then source_priority, And applies changes transactionally to reach a consistent final state, And employs optimistic concurrency with version checks to prevent races, And audit logs link events via a correlation_group_id and reflect the final applied order, And replaying the event set yields the same final state (idempotent and deterministic).
Retry, Backoff, and Failure Handling on Transient Errors
Given a mutation step fails due to a transient error (HTTP 429/5xx, network timeout) from a downstream dependency, When detected, Then the engine enqueues retries with exponential backoff and jitter up to 5 attempts over a maximum of 15 minutes, And no duplicate side effects occur across attempts (idempotent writes), And on success a resolving audit is written; on permanent errors (4xx) the event is marked failed without further retries, And partial changes are rolled back or compensated to maintain consistency, And the digest reflects counts of successes and failures with links to audit entries.
Slack/Teams Digest Delivery and Content
Given at least one qualifying change is processed within a 15-minute digest window, When the digest job runs, Then a compact summary message is delivered to the configured Slack/Teams channel within 5 minutes of window end, And entries are deduplicated per user with combined change types (e.g., New Hire • Team: Core • Location: NYC), And the message includes counts by type (created, updated, ignored, failed) and a link to detailed audit logs, And delivery retries on messaging API transient failures with exponential backoff, And no digest is sent if there are zero qualifying changes.
Activation Window & Admin Controls
"As a workspace admin, I want to control the 30-day activation window and rollout scope so that Drift Guard runs safely during our onboarding period."
Description

Activate Drift Guard automatically for the workspace’s first 30 days post-onboarding, with configurable start/end dates, pause/resume, and the ability to extend the window for phased rollouts. Provide per-team inclusion/exclusion lists, rate limits, and safety thresholds (e.g., maximum percentage of users changed per day). Surface a timeline view of applied changes within the window and a toggle to switch between Auto-Apply and Review-First modes.

Acceptance Criteria
Auto-Activation and Auto-Deactivation Window
Given a workspace completes onboarding at time T0 in the workspace time zone When no admin action is taken Then Drift Guard auto-activates immediately and sets the activation window from T0 to T0+30 days (inclusive) Given the current time is after T0+30 days and no extension is configured When the window end is reached Then Drift Guard auto-deactivates and no further changes are applied Given a workspace onboarded more than 30 days ago When Drift Guard has never been enabled Then auto-activation does not occur Given the activation window is displayed in the admin console When viewing the status banner Then the start and end timestamps are shown in the workspace time zone
Manual Start/End Date Configuration and Extension
Given Drift Guard is enabled for a workspace When an admin sets a custom start and end date/time where end > start Then the activation window updates immediately and the new dates persist Given the start date/time is in the future When the configuration is saved Then Drift Guard status shows Scheduled with the start timestamp, and auto-activation begins at that time Given an admin extends the end date/time beyond T0+30 days When the configuration is saved Then the activation window extends accordingly without service interruption Given an admin attempts to set an end date/time earlier than the start date/time or in the past relative to now When saving the configuration Then the system blocks the change and displays a specific validation error message
Pause and Resume Within Activation Window
Given Drift Guard is Active within its activation window When an admin clicks Pause Then no changes are auto-applied while paused, and the status changes to Paused with a timestamp Given Drift Guard is Paused When an admin clicks Resume Then processing resumes from the current time, and no backfill is applied automatically for the paused interval Given Drift Guard is Paused When new HRIS/IdP changes are detected Then they are queued according to the current mode (Auto-Apply queues as Pending; Review-First queues as Pending Review) and are not applied while paused
Per-Team Inclusion/Exclusion Enforcement
Given team inclusion and exclusion lists are configured When a user change is detected Then the change is processed only if the user's current team is in the inclusion list and not in the exclusion list Given a team is present in both inclusion and exclusion lists When a user change is detected for that team Then exclusion takes precedence and the change is not auto-applied Given an admin updates the inclusion/exclusion lists When the change is saved Then the new rules take effect immediately for subsequently detected changes, and an audit entry records who changed what and when Given a change is skipped due to exclusion When viewing the change details Then the decision rationale shows Team Excluded
Rate Limits and Safety Thresholds Enforcement
Given a daily maximum percentage threshold (e.g., 10%) is configured When the number of users with changes detected today would exceed the threshold Then auto-apply is capped at the threshold and remaining changes are queued for review with a warning status Threshold Exceeded Given a daily absolute cap (e.g., 100 users) is configured When changes detected today exceed the cap Then only up to the cap are processed and the remainder are queued for review Given thresholds are reached When viewing the admin console Then a banner and counter display the number capped and queued for the day and an audit entry records the threshold event Given Auto-Apply mode is enabled When thresholds are exceeded Then no additional changes are auto-applied beyond the configured limits
Timeline View of Applied and Pending Changes
Given the activation window is in effect When the admin opens the Drift Guard timeline Then the timeline lists detected changes within the window showing timestamp, source (HRIS/IdP), affected user, team, action (apply/queue/skip), status (Applied, Pending Review, Queued, Skipped, Reverted), and actor (system/admin) Given the admin applies filters by team, status, or date range When filters are applied Then the timeline results update to match the selected criteria and the filter state is preserved on refresh Given a change is applied or approved When the action completes Then the timeline updates within 60 seconds and shows the outcome and reason (e.g., Included Team, Threshold Cap) Given the activation window has ended When the admin opens the timeline Then the timeline is accessible in read-only mode with all filters functional
Auto-Apply vs Review-First Mode Behavior
Given Auto-Apply mode is selected When eligible changes are detected within the activation window and under thresholds Then the changes are applied automatically and logged with the system as actor Given Review-First mode is selected When eligible changes are detected within the activation window Then the changes are created as Pending Review items requiring explicit Approve or Reject by an admin Given a pending review item is Rejected When the admin rejects the change Then the change is not applied, its status becomes Rejected, and the timeline records the admin, timestamp, and reason Given the mode toggle is changed during the activation window When the admin switches modes Then the new mode applies to newly detected changes only, existing queued items retain their current state, and the mode change is audit-logged
Slack/Teams Compact Digest
"As a team lead, I want a concise Slack/Teams digest of what changed and what was applied so that I stay informed without digging into the app."
Description

Deliver a daily (or event-threshold-based) compact digest to designated Slack channels and Microsoft Teams, summarizing detected changes and applied updates: counts by type, notable exceptions, and links to details. Include actionable controls (Approve, Roll Back, Ignore Future Similar) where Review-First is enabled. Implement per-workspace and per-channel configurations, message formatting that respects platform limits, localization, and rate limiting to avoid notification fatigue.

Acceptance Criteria
Daily Slack Digest to Designated Channel
Given a workspace has Slack connected and a daily digest schedule set to 09:00 in the workspace timezone and a designated channel And at least one HRIS/IdP change was detected in the last 24 hours When the scheduled time occurs Then exactly one compact Slack message is posted to the configured channel containing: the covered date range, counts by type (new hires, team moves, location changes), number of applied updates, number of notable exceptions, top 3 exceptions with reasons, a View details link, and a stable digest ID And the Slack API responds HTTP 200 And no duplicate digest for the same time window is posted And if there are zero changes and Skip empty digests is enabled Then no message is sent; otherwise a No changes in the last 24h message is sent
Teams Digest on Event Threshold Exceedance
Given Microsoft Teams is connected and the digest trigger type is set to Event Threshold for a configured channel And the threshold is set to 10 detected changes since the last digest When the cumulative detected changes reach or exceed 10 Then a compact Teams adaptive card is posted to the configured channel summarizing: counts by type, notable exceptions (up to 3), number of applied updates, and a View details link And the Teams API responds with success and the card renders without truncation in the Teams client And the threshold counter resets to zero after the digest is sent
Actionable Controls with Review-First Enabled
Given Review-First mode is enabled for the workspace When a digest is posted to Slack or Teams Then the digest includes actionable controls: Approve, Roll Back, Ignore Future Similar, visible on the change groups requiring review And only users with Reviewer or Admin roles can invoke actions; others see a permission error When an authorized user clicks Approve on a change group Then only the pending updates in that group are applied within 60 seconds, the digest message updates to reflect new counts/status, and an audit log entry is recorded When an authorized user clicks Roll Back on a previously applied group Then the applied updates are reverted, the digest reflects the rollback, and an audit log entry is recorded When an authorized user clicks Ignore Future Similar on a change group Then a suppression rule is created for the selected similarity scope (e.g., type/team/location) that prevents similar changes from appearing in future digests for 30 days unless removed, and a confirmation is shown And all actions are idempotent and return success or error feedback to the invoker
Per-Workspace and Per-Channel Configuration
Given an admin configures separate Slack and Teams channels with distinct schedules or thresholds for a workspace When the configuration is saved Then the settings persist and become effective within 5 minutes And digests route to the specified channels according to their respective triggers And disabling a channel immediately stops further posts to that channel And multiple channels can be configured with different rules in the same workspace And validation prevents selecting archived or inaccessible channels And all configuration changes are audit-logged with actor, timestamp, and diffs
Localization, Formatting, and Platform Constraints
Given the workspace locale is set to fr-FR and timezone to Europe/Paris When a digest is posted to Slack and Teams Then all fixed strings are presented in French, dates/times use DD/MM/YYYY HH:mm, and numbers use locale-specific separators And for unsupported locales the content falls back to en-US And the message conforms to each platform's content limits such that the post succeeds without API validation errors and text is not visually truncated in the official clients And if content length would exceed limits, lists of exceptions are auto-collapsed beyond 3 items into a +N more link, while totals and links remain present
Rate Limiting and Change Coalescing
Given the per-channel rate limit is configured to a maximum of 1 digest per 30 minutes and 4 digests per day When multiple triggers occur within the cooldown window Then they are coalesced into a single digest sent at the next allowable time And the digest includes a note indicating coalesced N events since the last digest And no more than the configured maxima are sent within their rolling windows And metrics are emitted for suppressed and queued digests per channel
Secure Links to Details and Access Control
Given a digest includes View details links for further review When a user clicks a link Then a signed, single-use URL opens the detail view scoped to the digest's time window And the token expires after 24 hours and is bound to the originating workspace And RBAC enforces that only users with View Drift Guard permission can access the page; others receive a 403 response And all link accesses are audit-logged with user, workspace, and digest ID
Review Queue & One-Click Rollback
"As an approver, I want a review queue and one-click rollback so that I can catch and undo risky changes quickly."
Description

Provide an in-app review queue listing pending or recently applied changes with severity, impact scope, and recommended actions. Allow approvers to batch-approve, snooze, or reject items, and to perform one-click rollbacks that revert Timeglue entities to their prior state. Enforce role-based access control, capture reasoning notes, and ensure rollbacks propagate to scheduling constraints and notify affected owners when their hours or teams change.

Acceptance Criteria
Approver views Review Queue with pending and recently applied changes
Given there are at least 3 pending and 2 recently applied Drift Guard changes in the past 24 hours When an Approver opens the Review Queue Then each item displays severity (Low/Medium/High), impact scope (count of users and teams affected), recommended action, source system (HRIS/IdP), and change timestamp in the user’s local time And items are sorted by severity (High > Medium > Low) and then by most recent timestamp by default And the user can filter by item state (Pending/Applied/Snoozed/Rejected), severity, source, and team; applying a filter updates the list within 300 ms And the queue supports viewing at least 200 items via pagination or infinite scroll without error
Approver performs batch approve, snooze, and reject with reasoning notes
Given multiple pending items are selected in the Review Queue When the Approver clicks Approve Then all selected items are applied and move to Applied state; a success summary shows counts applied and any failures with reasons When the Approver selects Snooze and chooses a duration (1 hour, 1 day, 1 week, or custom up to 30 days) Then selected items move to Snoozed state and automatically re-enter Pending when the duration elapses When the Approver selects Reject Then a non-empty Reason note (minimum 5 characters) is required and items move to Rejected state with the reason stored And after any batch action the queue refreshes counts and item states without a full page reload within 1 second
Approver triggers one-click rollback on a change
Given a change has a recorded prior state snapshot When the Approver clicks One-Click Rollback and confirms with a required Reason note Then the system restores affected Timeglue entities (teams, work hours, holiday sets) to their immediately prior state And scheduling constraints are recalculated so meeting windows reflect the prior state within 60 seconds And dependent auto-schedules created in the last 24 hours as a result of the change are revalidated; any conflicts are flagged to owners If rollback partially fails, then the UI lists failed entities with error reasons and provides a retry option; successful sub-operations remain applied and all results are audit-logged
System sends owner notifications and hourly digest after change actions
When an approved change modifies a user’s working hours or team Then the affected owner receives a Slack/Teams DM (or email fallback) stating what changed, who acted, and when, within 2 minutes When a change is rolled back affecting a user’s hours or team Then the affected owner is notified via the same channel within 2 minutes And a compact digest is posted to the configured Slack/Teams channel at the top of the next hour summarizing counts by severity and action (Approved, Snoozed, Rejected, Rolled back) for the last hour with a link to the Review Queue Users without messaging integration receive an email digest within 10 minutes of the hour
RBAC enforcement for Review Queue visibility and actions
Given a logged-in user without Approver or Admin role When they open the Review Queue Then items are viewable but Approve, Snooze, Reject, and Rollback controls are disabled with a tooltip explaining required roles When a non-authorized user attempts any action via API Then the request is denied with HTTP 403 and an audit entry is created Given a user with Approver or Admin role When they access the Review Queue Then all actions are enabled and succeed subject to business rules And access is scoped to the user’s organization; cross-organization access is blocked
Audit trail captures action details and reasoning
When an Approver performs Approve, Snooze, Reject, or Rollback on any item Then the system records an immutable audit entry with actor, UTC timestamp, action, reason note (if provided or required), before/after snapshot, and list of impacted entities And audit entries are viewable in item detail and exportable to CSV; export completes within 30 seconds for up to 10,000 entries And audit entries are retained for at least 1 year and are searchable by item ID, actor, action, and date range
Performance, idempotency, and concurrency safeguards
Given 200 items exist When the Review Queue loads Then the first page renders within 2 seconds at the 95th percentile When an Approver runs batch Approve/Reject/Snooze on up to 50 items Then results are persisted and reflected in the UI within 1 second at the 95th percentile When an Approver rolls back a change affecting up to 500 users Then the rollback completes within 30 seconds at the 95th percentile And all actions are idempotent; retrying the same request within 5 minutes does not create duplicate side effects If an item changes after being loaded by an Approver Then attempting an action surfaces a conflict message and requires refresh; no stale write is committed
Integration Health Monitoring & Alerts
"As an admin, I want integration health monitoring and alerts so that I can resolve connector issues before they impact scheduling."
Description

Expose a health dashboard showing connector status, last successful sync, webhook latency, error rates, and backlog size. Implement automatic retries with exponential backoff, dead-letter queues for poison events, and alerting to admins when thresholds are breached or sync falls behind. Provide self-service diagnostics (test webhook, re-authorize connector), and exportable logs to SIEM. Ensure health signals are reflected in digests to keep zero-touch truly zero effort.

Acceptance Criteria
Per-Connector Health Dashboard Metrics Visibility
Given an org has at least two active connectors (e.g., HRIS and IdP) When an admin opens the Integration Health dashboard Then each connector displays Status (Healthy/Degraded/Down) computed as: - Healthy: error_rate < 1% over last 15m AND backlog < 100 AND last_successful_sync < 15m ago - Degraded: any metric outside Healthy but not Down - Down: no successful sync > 60m OR processing halted OR error_rate > 10% over last 15m And Last Successful Sync is shown in ISO 8601 and org local timezone And Webhook latency p95 for the selected window is shown in milliseconds And Error rate (%) over the selected window is shown with numerator/denominator tooltip And Backlog size (# events pending) is shown and updated in real time And the dashboard refreshes data at least every 60 seconds And metrics can be filtered by time range: 15m, 1h, 24h
Resilience: Retries and Dead-Letter Handling
Given a transient 5xx or timeout occurs during event processing When the processing attempt fails Then the system retries with exponential backoff starting at 2s, doubling each attempt, capped at 120s, with +/-20% jitter, up to 6 attempts And retries stop immediately upon first success And retries are rate-limited to max 5 concurrent retries per connector And attempt count and total retry duration are recorded in logs Given an event fails due to non-transient causes (4xx, schema validation) or exceeds max attempts When processing concludes Then the event is moved to a dead-letter queue (DLQ) with payload, headers, error type/message, first_seen_ts, last_attempt_ts, attempts_count, connector_id, correlation_id And the DLQ count is visible on the dashboard and supports drill-down And admins can replay single or batch DLQ items with confirmation and rate limiting; replay preserves idempotency keys And DLQ items are retained at least 14 days and are exportable as NDJSON
Threshold-Based Alerting to Admins
Given alert thresholds are configured (defaults: error_rate > 2% over 15m OR backlog > 1000 OR last_successful_sync > 30m) When any threshold is breached for 2 consecutive measurement intervals Then an alert is sent to the org's configured channels (Slack/Teams; email if enabled) within 2 minutes And the alert includes connector name, metric breached, current value, threshold, first_seen timestamp, and a link to the health dashboard And alerts are deduplicated to at most 1 notification per connector+metric per 30 minutes until recovery And a recovery notification is sent when the metric remains below threshold for 10 consecutive minutes And all alerts and recoveries are recorded in the audit log
Self-Service Diagnostics: Test Webhook
Given an admin clicks the "Test Webhook" action for a connector When the system sends a signed synthetic event to the configured endpoint Then the UI displays within 10 seconds: remote HTTP status code, end-to-end latency (ms), signature verification result, and the first 20 lines of the response body And the test result is stored in logs with correlation_id and is exportable And if the failure is due to auth/permission, the UI surfaces a "Re-authorize Connector" call-to-action And the connector status and webhook latency metrics update within 60 seconds of the test
Self-Service Diagnostics: Re-Authorize Connector
Given a connector's OAuth token is expired or revoked When an admin initiates "Re-authorize Connector" Then the OAuth flow completes without altering existing team mappings or historical state And upon success, a background incremental sync is queued within 5 minutes And the connector status transitions to Healthy only after 5 consecutive minutes of normal metrics And the audit log records actor, timestamp, connector_id, and granted scopes And if re-authorization fails, a clear error is shown with retry guidance and a link to documentation
Exportable Logs to SIEM
Given an admin configures SIEM export with HTTPS endpoint and API key When log export is active Then logs are delivered as JSON Lines with fields: ts (ISO 8601), org_id, connector_id, event_id, severity, message, context, attempt, status, latency_ms, error_code, correlation_id And delivery is at-least-once with idempotency keys, exponential backoff on 429/5xx, and retries capped at 24h And admins can request backfill using from_ts/to_ts and receive all matching records without duplicates And the same logs can be downloaded from the UI as CSV for a selected time window And PII fields are masked per org data policy settings
Health Signals in Slack/Teams Digest
Given the daily digest schedule occurs within workspace quiet hours respected When the digest is sent to Slack/Teams Then it includes per-connector status (Healthy/Degraded/Down), last successful sync, max backlog in last 24h with current backlog, top 3 error causes, and links to the health dashboard and quick actions (Test Webhook, Re-authorize) And if all connectors are healthy with no breaches in last 24h, the digest is a single-line "All clear" with dashboard link And timestamps in the digest are localized to each recipient's timezone And the digest message size remains under 3,000 characters to avoid truncation

Auto Announce

Automatically generates SSO‑gated smart links and posts a welcome brief to Slack/Teams explaining booking windows and guardrails. Everyone knows where to book and what to expect—no training session required.

Requirements

SSO-Gated Smart Link Generation
"As a team lead, I want to share smart links that require company login so that only authorized teammates can book within our defined windows."
Description

Automatically creates unique smart booking links that require SSO authentication (e.g., Okta, Google Workspace, Azure AD) before access. Links are scoped to organization/team/role, carry booking window parameters, support expiration policies (time-based, single-use, multi-use), and are time zone aware. Enforces permission checks at click time and maps the authenticated user to their org record. Integrates with Timeglue’s identity service and scheduling engine; emits audit events on creation and use for compliance and observability.

Acceptance Criteria
SSO Authentication Required for Smart Link Access
Given a valid smart link and an unauthenticated visitor When the link is opened Then the visitor is redirected to the configured SSO provider (Okta, Google Workspace, or Azure AD) and no booking data is displayed until authentication completes Given a visitor with an active SSO session for the configured provider When the link is opened Then the booking flow loads without an additional login prompt and the session is recognized by the identity service Given SSO authentication fails (denied, expired, or invalid state) When the user returns to Timeglue Then the page displays an authentication error and no booking data is displayed
Permission Checks and Scope Enforcement at Click
Given a smart link scoped to organization=OrgA, team=Team1, role=Manager When an authenticated user from a different organization opens it Then access is denied with HTTP 403 and reason=ORG_MISMATCH and no booking data is displayed Given a user in OrgA but not a member of Team1 or role=Manager When opening the link Then access is denied with HTTP 403 and reason=SCOPE_MISMATCH Given a user whose identity is mapped to OrgA/Team1/Manager by the identity service When opening the link Then access is granted and the booking flow loads Given membership changes after link creation When the link is opened Then authorization uses current membership from the identity service at click time
Booking Window Parameters and Scheduling Engine Enforcement
Given a smart link with parameters window_days=14, allowed_hours=09:00–17:00 organizer local, min_duration=30m, max_duration=60m, and focus_blocks respected When the booking UI loads Then only slots within those constraints are returned by the scheduling engine and displayed Given a user attempts to book a slot outside the allowed window by altering request parameters When the booking request is submitted Then the server rejects it with HTTP 422 and error=OUT_OF_POLICY and no booking is created Given focus blocks and holidays are present in the organizer’s calendar When slots are computed Then those times are excluded from the returned availability Given a successful booking within constraints When confirmation occurs Then the scheduling engine records the event and the user sees a confirmation page with a unique booking ID
Expiration Policy: Time-Based Expiry
Given a smart link with expires_at=2025-09-30T23:59:59Z When accessed after that timestamp (allowing up to 60s clock skew) Then the server returns HTTP 410 and message=LINK_EXPIRED and no booking data is displayed Given the same link before expiration When accessed Then the booking flow loads normally
Expiration Policy: Single-Use and Multi-Use
Given a single-use smart link When an authenticated user completes a booking using the link Then subsequent accesses to the link return HTTP 410 with message=LINK_REDEEMED and no booking data is displayed Given a single-use smart link that was opened but no booking was completed When the page is closed and reopened Then the link remains usable until the first booking is confirmed Given a multi-use link with max_uses=5 When bookings 1 through 5 are confirmed via the link Then each succeeds Given a multi-use link with max_uses=5 When a 6th booking attempt is made via the link Then HTTP 410 with message=MAX_USES_REACHED is returned and no booking is created
Time Zone Awareness and DST Correctness
Given a link anchored to the organizer’s time zone America/New_York When viewed by a user in Europe/Berlin Then the displayed slots are converted to the viewer’s local time while enforcing the organizer’s 09:00–17:00 local window, including correct handling across DST transitions Given the viewer changes their device/browser time zone When the page is refreshed Then all slot times update to the new local time zone without changing which organizer-local windows are allowed Given a DST transition date in the organizer’s zone When slots for that date are displayed Then there are no duplicate or missing hours beyond the DST rule and slot boundaries remain aligned within ±1 minute
Audit Events on Link Lifecycle (Create, Access, Redeem)
Given a smart link is created When creation succeeds Then an audit event smart_link.created is emitted within 5 seconds containing link_id, creator_user_id, org_id, scope, expiry_policy, and correlation_id Given a smart link is accessed When authorization completes Then an audit event smart_link.accessed is emitted within 5 seconds containing link_id, auth_user_id (or null), org_id (resolved), access_result (granted|denied), reason_code, and request_id Given a booking is confirmed via the link When confirmation occurs Then an audit event smart_link.redeemed is emitted within 5 seconds containing link_id, booking_id, auth_user_id, org_id, and request_id Given the audit pipeline is temporarily unavailable When an event is queued Then it is retried with backoff for at least 24 hours and delivered exactly once per event_id
Slack/Teams Connection & Auto Post
"As an admin, I want Timeglue to auto-post the booking brief in our chosen channels so that everyone knows how and where to schedule without a walkthrough."
Description

Integrates with Slack and Microsoft Teams via OAuth to automatically post a welcome brief that includes SSO-gated smart links, booking windows, guardrails, and “how to book” guidance. Supports selecting target channels, @mentions, and DM to new members; posts using platform-appropriate rich blocks/cards with link unfurling. Captures message IDs for updates/edits, respects workspace permissions and rate limits, and offers a settings UI to configure announcement timing and destinations.

Acceptance Criteria
OAuth Connection to Slack and Microsoft Teams
Given a Timeglue org admin initiates “Connect Slack” or “Connect Teams”, When the OAuth consent screen is completed, Then the connection is established using the documented minimum scopes and the connected workspace/tenant name and ID are shown in Settings. Given required scopes are not granted, When the user returns from OAuth, Then the connection is rejected with a clear list of missing scopes and a one-click retry. Given a valid connection exists, When the token expires or is revoked, Then Timeglue detects the failure on the next API call and prompts for reconnection without attempting further posts. Given multiple Slack workspaces or Teams tenants are accessible to the admin, When connecting, Then the user can select the intended workspace/tenant and Timeglue stores the selected ID only. Given a successful connection, Then access tokens are stored encrypted at rest and are never displayed in the UI or logs.
Post Welcome Brief to Selected Destinations
Given destinations (channels and/or teams) are configured, When Auto Announce is triggered, Then Timeglue posts exactly one welcome brief per destination using Slack Block Kit or Teams Adaptive Card respectively. Given the welcome brief is posted, Then it includes: SSO‑gated booking link(s), booking windows summary, guardrails, and step‑by‑step “how to book” guidance. Given a scheduled post time is set, When the time occurs, Then the post is delivered within ±1 minute of the scheduled time. Given a destination channel does not permit bot posting, When attempting to post, Then Timeglue surfaces a permission error and does not retry that destination. Given a post is successful, Then the message visibly renders without markdown or layout errors on both platforms.
SSO‑Gated Smart Links and Link Unfurling
Given a recipient clicks a smart link from Slack/Teams, When not authenticated, Then they are prompted to sign in via SSO and, upon success, land on the booking page preloaded with the intended scheduling context. Given the recipient is authenticated, When opening the smart link, Then the booking UI reflects the configured booking windows and guardrails exactly. Given the smart link is posted in Slack/Teams, Then the link unfurls to show a title, description, and icon without exposing private availability details; unfurl loads within 2 seconds on a typical broadband connection. Given a smart link is revoked by an admin, When the recipient clicks it, Then an access denied page is shown and the unfurl no longer previews sensitive details.
@Mentions and Direct Messages to New Members
Given an admin configures @mentions (users, user groups, or channels), When the welcome brief is posted, Then the mentions resolve to correct user/group IDs and notify the mentioned parties. Given “DM new members” is enabled, When a new member joins the connected workspace, Then Timeglue sends the DM welcome brief within 2 minutes and only once per member. Given a user has DMs from apps disabled or the bot lacks DM permission, When attempting to DM, Then Timeglue logs a non-fatal error and skips that user without blocking other deliveries. Given a member has previously received the DM, When they leave and rejoin within 30 days, Then a duplicate DM is not sent.
Message IDs, Edits, and Idempotent Updates
Given a welcome brief is posted, Then Timeglue stores the platform message ID, channel/conversation ID, and timestamp for each destination. Given an admin edits the announcement content or destinations, When updates are applied, Then existing posts are updated in place using the stored message IDs; if the platform disallows editing, a threaded update is posted and the UI indicates which destinations were threaded. Given a retry or duplicate trigger occurs, When posting, Then the operation is idempotent per destination (same idempotency key) and does not create duplicate posts. Given an update succeeds, Then the stored metadata is refreshed to reflect the latest message IDs/timestamps.
Permissions, Errors, and Rate-Limit Handling
Given API responses return insufficient_scope, channel_not_found, or 403, When posting, Then Timeglue marks the destination as failed, provides a human‑readable reason, and does not retry until configuration changes. Given the provider returns a 429 rate‑limit, When posting, Then Timeglue retries with exponential backoff (initial 1s, factor 2, jitter, max 3 retries, cap 30s) and respects Retry‑After headers when present. Given partial failures across multiple destinations, When the job completes, Then the UI shows a per‑destination success/failure summary and offers a one‑click retry for failed destinations only. Given an unexpected 5xx error, When posting, Then Timeglue logs correlation IDs and request metadata without secrets and retries once after 2 seconds.
Settings UI for Timing, Destinations, and Preview
Given an admin opens Auto Announce settings, Then they can: select Slack/Teams workspace, choose channels/teams, toggle DM to new members, and set timing (immediate, scheduled date/time, or event‑based triggers). Given required fields are incomplete or invalid, When saving settings, Then validation errors are shown inline and the settings are not saved. Given settings are saved, Then a success toast appears and the configuration persists; changes are auditable with timestamp and actor. Given a preview is requested, When rendering preview, Then the UI shows a platform‑specific preview (Slack blocks or Teams card) with live data and a test‑post option to a private channel.
Welcome Brief Template Engine & Localization
"As a team lead, I want to tailor the brief’s content and language so that it matches our brand and clearly communicates our scheduling rules."
Description

Provides a template system for the welcome brief with variables for team name, core hours, time zones, holidays, focus blocks, rescheduling policy, and help links. Supports multi-language localization, company branding (logo/colors), and platform-specific rendering (Slack blocks, Teams cards). Includes a preview editor, token validation, versioning, and the ability to update previously posted messages in place; allows A/B variants and per-team overrides.

Acceptance Criteria
Token Rendering & Required Variables
Given a saved welcome brief template containing supported tokens {team_name}, {core_hours}, {time_zones}, {holidays}, {focus_blocks}, {reschedule_policy}, and {help_links} When the template is rendered for a team with corresponding configuration Then each token is replaced with the correct value and no unresolved tokens remain. Given the template contains any unsupported or misspelled token When preview or publish is attempted Then the system blocks the action and surfaces a validation error identifying the token and its location. Given required data for any supported token is missing in the team configuration When rendering is attempted Then a blocking validation error names the missing variable(s) and prevents publishing until resolved. Given help_links are provided When validation runs Then each link must be a valid HTTPS URL; otherwise publishing is blocked with a specific error for the offending link(s).
Localization & Fallback Behavior
Given a team default language is set and a user preview language is selected When generating or previewing the welcome brief Then all static strings are localized to the preview language and date/time formats reflect that locale. Given a translation key is missing in the selected language When generating or previewing the welcome brief Then the system falls back to the team default language for the missing keys and records a missing-translation warning. Given neither the selected nor team default language provides a translation for a key When generating or previewing the welcome brief Then the system falls back to the system default language and does not block publishing. Given the user switches the preview language When the selection changes Then the preview re-renders in the new language within 500 ms without a full page reload.
Branding (Logo & Colors) Application
Given organization branding is configured with a logo and primary/secondary colors When rendering the welcome brief for Slack and Teams Then the logo is displayed and color accents are applied to supported components while respecting each platform’s guidelines. Given the logo is missing or the logo URL is invalid When rendering Then a default placeholder logo is used and a non-blocking warning is shown in the editor. Given a platform does not support a specified brand color for a component When rendering for that platform Then the platform’s default color is used without blocking or visual breakage.
Platform-Specific Rendering (Slack Blocks & Teams Cards)
Given a valid template When rendering for Slack Then the output is valid Block Kit JSON that passes schema validation and renders without errors or warnings. Given the same template When rendering for Microsoft Teams Then the output is a valid Adaptive Card payload that passes schema validation and renders without errors or warnings. Given the template contains an element unsupported on a target platform When rendering for that platform Then the system applies a defined fallback or omits the element with a warning, and the message remains structurally valid and informative. Given the template contains hyperlinks When rendered on Slack or Teams Then link formatting matches the target platform’s syntax and links are clickable.
Preview Editor & Inline Token Validation
Given a template is open in the preview editor with a sample team selected When the author edits content or tokens Then the live preview updates within 500 ms to reflect changes using the sample team’s data. Given an invalid or unsupported token is present in the template When focus leaves the token or a validation run is triggered Then the editor highlights the token and displays an inline error tooltip; the Publish action is disabled until blocking errors are resolved. Given the author switches the sample team or preview language When the selection changes Then the preview re-renders using the selected team’s configuration and language without reloading the page.
Versioning & In-Place Message Updates
Given a welcome brief version v1 has been posted to Slack or Teams When a new version v2 of the same template is published with Update in place selected Then the original message is updated in the same channel/thread without creating a new message, and the message permalink remains unchanged. Given v2 has been published When the author performs a rollback to v1 and confirms Then the posted message is reverted to v1 content and the version history records the rollback event with timestamps and actor. Given the original posted message has reactions or thread replies When it is updated in place Then all reactions and thread replies remain intact.
A/B Variants & Per‑Team Overrides Precedence
Given two active variants A and B with a 50/50 allocation and no team overrides When 200 welcome briefs are posted across eligible teams Then the observed distribution of variants is within ±5% of the configured split. Given a team has a defined template override When Auto Announce posts the welcome brief for that team Then the team’s override template is used and any A/B assignment is ignored. Given variant assignment is deterministic by team and channel When multiple posts occur for the same team and channel without configuration changes Then the same variant is used consistently across posts.
Booking Window Computation & Sync
"As a manager, I want the announcement to always show accurate, policy-compliant windows so that meetings respect our team’s constraints."
Description

Derives and maintains “sane” cross-region booking windows by aggregating members’ working hours, holidays, and focus blocks from connected calendars. Applies guardrails (no after-hours invites, buffers, max early/late offsets, notice periods) and recalculates when calendars or policies change. Exposes windows as structured data for message templates and link parameters, with caching and invalidation on relevant updates.

Acceptance Criteria
Cross-Region Window Aggregation from Connected Calendars
Given a team with connected Google/Microsoft calendars for all members and imported working hours, holidays, and focus blocks And a scheduling policy: slotLength=30m, bufferBefore=10m, bufferAfter=10m, maxEarly=60m, maxLate=60m, minNotice=12h, horizon=14d When booking windows are computed for the next 14 days (rolling) Then each window is the intersection of members’ allowable hours (working hours expanded by maxEarly/maxLate) minus holidays and focus blocks And each window excludes time required to satisfy bufferBefore and bufferAfter for all members And no window starts earlier than now + minNotice in any participant’s local time And all windows are returned as non-overlapping ISO-8601 intervals with explicit IANA time zone IDs, sorted ascending And all window boundaries align to a 15-minute grid and each window length is >= slotLength
Guardrail Enforcement on Out-of-Policy Attempts
Given computed windows exist for a team and a smart link is generated from those windows When a consumer requests a slot that falls partially or fully outside any computed window due to after-hours, holiday, focus block, buffers, or notice period Then the slot is not offered in UI selection lists And API requests to hold or book that slot are rejected with HTTP 422 and error code "slot_outside_window" And an alternative windows payload is returned with the current valid windows and a machine-readable reason for rejection
Reactive Recalculation on Calendar or Policy Change
Given booking windows have been computed and cached And a relevant change occurs (new/updated/deleted event, holiday, focus block, working-hours change via provider webhook; or policy change via admin API) When the change is received Then affected teams’ windows are recomputed within 60 seconds And the windows payload version is incremented and timestamped And any cached entries for affected keys are invalidated immediately And smart link parameters generated after recomputation reference the new version And any fetch using the prior ETag within 5 minutes returns 200 with fresh content (no stale windows past 5 minutes)
Caching, ETag, and Stale-While-Revalidate Behavior
Given a windows fetch is performed via API for a team with no relevant changes When the same request (same params) is repeated within a 15-minute TTL Then the response is served from cache with header X-Cache: HIT and includes an ETag And response time p95 is <= 100ms When the TTL expires or an invalidation event occurs Then the next request returns X-Cache: MISS with a new ETag And If-None-Match with an unchanged ETag returns 304 within TTL; after recomputation returns 200 with updated payload
Daylight Saving and Time Zone Transition Robustness
Given at least two members undergo a DST offset change within the 14-day horizon When booking windows are computed spanning the transition Then each member’s local hours are respected using their post-change UTC offset on and after the transition instant And windows on the transition day neither double-count nor skip slot durations (slotLength remains constant) And no window crosses midnight if policy forbids cross-day slots; otherwise cross-day windows include correct date boundaries And all returned intervals validate against each member’s local IANA zone without offset errors
Structured Data Exposure for Templates and Smart Link Parameters
Given booking windows are computed for a team When a client requests windows for message templating and link generation Then the API returns JSON with fields: teamId, policyVersion, generatedAt, timeZoneStrategy, and windows:[{start,end,tz,slotLengthMinutes}] And includes summary fields earliestStart, latestEnd, totalWindowMinutes, and reasonCodes if empty And smart link parameters (e.g., start, end, tz, policyVersion, signature) reconstruct the same offered windows set on the booking page And tampering with parameters invalidates the signature and yields HTTP 401 with error code "invalid_signature"
Link Lifecycle Management & Revocation
"As an admin, I want to control and audit shared links so that access stays secure and messages stay current."
Description

Adds an admin console to list, search, and manage auto-announced links and their associated posts. Supports revoking, expiring, or regenerating links; rotating tokens; bulk updates; and automatic invalidation when members leave or policies change. Provides per-link analytics (views, auth successes, bookings started/completed) and tools to re-post or edit the original brief thread with updated links.

Acceptance Criteria
Admin Lists and Filters Auto-Announced Links
Given an authenticated org admin When they open the Link Management page Then a table loads within 2 seconds with columns: Link ID, Owner, Channel/Team, Status, Created At, Last Used, Policy Version, Original Post URL Given more than 1,000 links exist When the admin paginates or sets page size to 50 Then results are server-paginated and the next page loads within 1 second Given the admin applies any combination of filters (Status, Owner, Channel, Date Range, Policy Version) When filters are applied Then results reflect the intersection of filters and the total count updates accordingly Given the admin enters a search term matching link ID, owner email, or channel name When search is executed Then only matching rows are shown (case-insensitive), with matches highlighted Given a row is selected When the admin opens its details Then a details panel opens within 500 ms showing actions: View Analytics, Revoke, Expire, Regenerate, Rotate Token, Edit/Repost, Copy URL
Revoke Single Link and Invalidate Access Immediately
Given an active link When an admin clicks Revoke and confirms Then the link status changes to Revoked and all existing tokens are invalidated globally within 10 seconds Given the revoked link URL is visited (authenticated or not) When the page loads Then the system shows an access-block screen with HTTP 410/403 and booking cannot be started Given the original Slack/Teams brief thread exists When the link is revoked Then a reply is posted in that thread within 15 seconds stating the link was revoked, including timestamp and admin name Given analytics for the link When viewed after revocation Then no new Bookings Started or Bookings Completed events are recorded with timestamps after the revocation time
Set Expiration and Token Rotation
Given an active link When an admin sets an expiration date/time in the org timezone and saves Then the expiration persists and is visible on the list and details views Given the expiration time is reached When the next access attempt occurs Then the link status is Expired and access is blocked within 60 seconds with an "Link expired" message Given an admin clicks Rotate Token (now) When the action completes Then a new token is issued, the URL updates, prior tokens stop working immediately, and analytics/ownership remain unchanged Given a rotation schedule of every N days is configured When the scheduled time occurs Then rotation executes at 02:00 in the org timezone with audit entries for success/failure
Bulk Regenerate Links and Update Brief Threads
Given an admin selects 10–200 links in the list When they choose Bulk > Regenerate & Update Posts and confirm Then the system regenerates each link's token, edits the original Slack/Teams brief message to update the link, and posts a single threaded summary reply per thread indicating the update Given the bulk job runs When it completes Then the UI shows per-item status (Success/Failed) and totals; overall throughput is at least 60 links per minute Given any item fails due to missing permissions or deleted thread When the job completes Then the item is marked Failed with a retry option and an exportable error report (CSV) Given bulk changes are applied When a user clicks any updated link Then they land on the new URL and can authenticate via SSO with no additional prompts beyond standard login
Auto-Invalidate on Offboarding or Policy Change
Given a SCIM/IdP deprovision event is received for a user who owns links When the event is processed Then all links owned by that user are set to Revoked within 5 minutes, and a revocation notice is posted to each original thread Given an org policy is updated to tighten booking guardrails (e.g., work hours narrowed or external booking disabled) When the policy is published Then affected links are either updated to enforce new rules or invalidated as required within 5 minutes, with an audit note of the policy version applied Given a previously valid link now conflicts with policy When it is accessed Then booking times outside the new guardrails are not offered, and if fully disallowed, access is blocked with a policy change notice
Per-Link Analytics with Time Range and Export
Given a link details view When opened Then it shows counters for Views, Auth Successes, Bookings Started, and Bookings Completed for selectable ranges: 24h, 7d, 30d, Custom Given analytics are displayed When 5 minutes have passed Then the displayed metrics auto-refresh with a last-updated timestamp Given Export CSV is clicked with a selected date range When the export completes Then a CSV downloads containing per-day rows and totals with fields: Date, Views, Auth Successes, Bookings Started, Bookings Completed, Timezone, and Link ID Given privacy controls When analytics are exported Then no PII beyond owner email and channel name appears in the CSV
Edit Original Brief Thread In-Place Without Duplicates
Given an admin clicks Edit Brief on a link When they update booking windows/guardrails text and save Then the original Slack/Teams brief message is edited in-place (same message ID), preserving reactions and thread Given the workspace disallows message edits When the change is saved Then the system posts a single "Updated Booking Info" reply instead, and subsequent edits update that same reply rather than creating duplicates Given the brief includes the smart link When the edit is saved Then the visible link in the brief points to the current tokenized URL, and there is at most one current link visible in the thread Given a user opens the brief via Slack/Teams When they click the link post-edit Then they are routed to the updated link and SSO enforcement still applies
Delivery Reliability, Retries, and Fallbacks
"As a product owner, I want dependable delivery with clear status and fallbacks so that no team misses the announcement."
Description

Implements a durable, idempotent job pipeline for announcement creation and posting with exponential backoff retries and dead-letter handling. Monitors platform rate limits and spreads posting accordingly. Surfaces delivery status in an ops dashboard, alerts on failures, and falls back to email delivery with the same brief when chat posting is unavailable. Guarantees exactly-once posting per target and supports manual re-run by admins.

Acceptance Criteria
Exactly-Once Chat Posting per Target
Given an Auto Announce job with a unique announcement_id and a specific target channel And prior attempts may have partially executed When the job is executed or re-executed due to retry or manual re-run Then at most one message exists in the target channel associated with the announcement_id within 5 seconds after completion And duplicate post attempts are prevented via idempotency keys, producing no user-visible duplicates And the final target status is "Posted" with the message permalink recorded
Exponential Backoff Retries with Dead-Letter and Alerting
Given transient posting failures occur (e.g., HTTP 5xx or network timeouts) When a post attempt fails Then the system retries with exponential backoff starting at 2s, doubling each attempt with ±20% jitter, up to 6 attempts or a maximum 2-minute delay, whichever occurs first And if retries are exhausted or a non-retriable error occurs (4xx excluding 429), the job is moved to the dead-letter queue within 10 seconds And an alert is sent to the on-call channel and incident system containing job_id, announcement_id, target, error code, and failure summary
Rate Limit Detection and Post Spreading
Given the chat platform signals rate limits (HTTP 429 and/or rate-limit headers) And multiple targets share the same tenant within a posting batch When processing posts for that tenant Then the system enforces per-tenant concurrency of 2 by default (configurable) and defers additional posts until the provided Retry-After or reset time And no request is sent before the Retry-After elapses And the batch completes without additional 429 responses after the first detected limit for that tenant And overall completion for K targets stays within the scheduled window plus 5 minutes
Email Fallback When Chat Post Unavailable
Given the chat workspace is disconnected or the platform is unavailable (persistent 5xx beyond retry limits) When the Auto Announce pipeline processes the job Then an email with the same welcome brief and the SSO-gated smart link is sent to the designated distribution within 2 minutes And the email subject and body include booking windows, guardrails, and the announcement_id And the ops dashboard records status "Fallback: Email Sent" with SMTP message-id and timestamp And no chat message is posted for that target
Ops Dashboard Status and Telemetry
Given a batch of Auto Announce jobs is running When an operator views the ops dashboard Then each job displays per-target status: Pending, In-Progress, Posted, Fallback: Email Sent, or Failed (DLQ) And shows attempt_count, last_attempt_at, last_error_code, rate_limit_state, and message permalink or SMTP message-id And state changes are visible in the dashboard within 5 seconds of occurrence And filtering by announcement_id or tenant returns results within 1 second for up to 10,000 jobs
Admin Manual Re-Run with Idempotency
Given an admin selects a Failed or Dead-Lettered job in the ops dashboard When they trigger Re-run Then a new execution is queued with the same announcement_id and idempotency keys And if the original post exists, no duplicate is created; the status becomes Posted and the audit log records "Re-run: No-Op (Already Posted)" And if not posted, the pipeline executes with standard retry/backoff and rate-limit handling And all actions are captured in an immutable audit log with actor, timestamp, and outcome
SSO-Gated Smart Link Generation Idempotency
Given an Auto Announce job begins When generating smart links for the target audience Then a single SSO-gated link is created per audience scope and announcement_id with a TTL of at least 30 days, adhering to org policy And repeated generation attempts return the same link without creating duplicates And access to the link requires successful SSO; unauthorized access returns 401 with no booking access And the link and brief content match exactly across chat and email deliveries

Masked Blocks

Publish availability as open/closed windows at configurable granularity (e.g., 1–3 hour bands) with optional fuzzed edges. External viewers can request within a block but never see exact times, preserving privacy while keeping coordination fast and respectful.

Requirements

Configurable Block Granularity
"As a host, I want to set the granularity of published availability so that external viewers only see broad windows aligned to my preferences."
Description

Enable hosts to publish availability as open/closed windows at selectable granularity (e.g., 30 min, 1 hr, 2 hr, 3 hr). The system rounds underlying exact availability to the chosen band size and displays only the bands, never precise start/end times. Granularity is configurable globally and per share link, with sensible defaults from meeting templates (duration, buffers). The scheduler maps requests captured at the band level to actual free slots internally, honoring calendars, buffers, and meeting length. Viewer UI shows bands aligned to the viewer’s time zone and locale. Implementation integrates with existing availability computation and supports edge cases like daylight saving transitions and partial-day holidays.

Acceptance Criteria
Per-Link Granularity Override
Given the host account default granularity is 1 hour and the allowed options are {30m, 1h, 2h, 3h} And the host creates a share link for a meeting template with per-link granularity set to 2 hours When a viewer opens the share link Then the availability timeline renders in 2-hour bands aligned to the viewer’s local timezone And exact start/end times are not displayed anywhere in UI text, tooltips, ARIA labels, or network payloads for the viewer route And analytics and logs tied to the viewer request do not include precise start/end timestamps
Template-Driven Default Granularity
Given a meeting template with duration D and total buffers B When a host creates a new share link without explicitly setting granularity Then the default granularity equals the smallest allowed band size that is >= D + B And the default is one of {30m, 1h, 2h, 3h} And the chosen default is persisted with the link and used for rendering on all viewer sessions
Band Rounding and Display Privacy
Given the host’s exact availability windows include non-aligned minute offsets (e.g., 9:15–10:05) And granularity is set to 1 hour When the timeline is computed for public display Then open windows are rounded to 1-hour band boundaries without revealing sub-band gaps And the UI displays labels like “9–10”, “10–11” in the viewer’s locale, never “9:15–10:05” And public APIs for the share link return only band identifiers or band boundary times, not internal exact availability timestamps
Viewer Timezone and Locale Alignment
Given a viewer in timezone America/Los_Angeles with locale en-US And the share link granularity is 2 hours When the viewer opens the link on a non-DST-transition day Then bands are labeled and positioned according to the viewer’s timezone and 12/24h locale preferences (e.g., 8–10 AM) And changing the viewer’s timezone to Europe/Berlin re-renders the same bands aligned to Europe/Berlin without altering the link’s stored configuration And date formats and weekday names reflect the viewer’s locale
Request Mapping to Underlying Free Slot (Success)
Given a share link with granularity 2 hours and a visible 10–12 band that contains at least one internal free slot satisfying meeting duration and buffers When the viewer submits a request selecting the 10–12 band Then the scheduler books the earliest qualifying underlying free slot within that band honoring host calendars, buffers, and meeting length And the viewer confirmation references only the selected band window (not the exact start time) And the host’s calendar event is created at the exact internal start/end time
Request Mapping When No Valid Slot in Selected Band
Given a share link with granularity 1 hour and a visible 3–4 PM band that, after applying buffers and conflicts, contains no continuous free slot of required length When the viewer submits a request selecting the 3–4 PM band Then the request is rejected with a clear message indicating no availability within the selected window and suggesting adjacent open bands And no precise times or partial internal availability are exposed in the UI or response payload
DST Transition and Partial-Day Holiday Handling
Given a viewer in a timezone with a spring-forward transition on the selected date And granularity is 1 hour When the day containing the DST skip is displayed Then bands spanning the missing local hour are not rendered and remaining bands reflect correct local wall-clock durations And on a fall-back day, duplicated clock hours do not create duplicate overlapping band labels; each band maps to a unique UTC interval And if a partial-day holiday (e.g., 13:00–17:00 local) applies, no open bands intersect the closed interval
Fuzzed Edge Boundaries
"As a host, I want masked block edges to be fuzzed within a safe margin so that exact start and end times cannot be inferred."
Description

Provide optional fuzzing of masked block boundaries to mitigate inference of exact availability. Apply a configurable jitter window (e.g., ±10–20 minutes) to band edges while guaranteeing the displayed block always contains at least the meeting duration within actual working hours. Use deterministic per-link pseudorandomization so multiple visits by the same viewer are consistent, while different links cannot be correlated. Ensure fuzzing never exposes times outside allowed work hours, holidays, or focus blocks. Implement guardrails for minimum visible coverage and compatibility with variable granularity. Include host-side preview to visualize how blocks will appear to recipients.

Acceptance Criteria
Configurable Jitter Window Applied to Block Edges
Given a masked block with true start S and end E and fuzzing enabled with jitterRange = ±[10,20] minutes per edge for the link When a recipient loads the link Then the visible start S' and end E' are each offset from S and E by deterministic values within [-20m,-10m]∪[10m,20m], unless guardrails clamp them And if fuzzing is disabled for the link, S' = S and E' = E And start and end offsets are computed independently from the same per-link seed
Minimum Visible Coverage Meets Requested Duration
Given configured meeting duration D and an original block [S,E] that contains at least one contiguous interval of length D within allowed working time When fuzzing is applied to produce [S',E'] Then there exists at least one contiguous interval of length ≥ D fully inside [S',E'] and within allowed working time And if the initial offsets would violate this condition, the offsets are clamped minimally so the condition holds
Deterministic Per-Link Fuzz Consistency and Cross-Link Non-Correlation
Given the same shared link visited multiple times by different devices and sessions When fuzz offsets are calculated Then S' and E' are identical across all visits for that link Given 100 different links with identical underlying [S,E] and identical jitter configuration When fuzz offsets are calculated for each link Then at least 95% of links produce offset pairs (start,end) different from any other link in the batch And offsets do not vary based on viewer identity, IP, cookies, or request time
Fuzzing Respects Work Hours, Holidays, and Focus Blocks
Given organizational working hours, holiday calendars, and focus blocks that define allowed time When a computed S' or E' would fall outside allowed time Then that boundary is clamped to the nearest allowed boundary so that no visible time lies outside allowed time And if clamping eliminates all visible coverage for the block, the block is not shown
Granularity Compatibility and Minimum Band Width Guardrails
Given band granularity G ∈ [1h,3h], meeting duration D, and an original block duration L = E−S When fuzzing is applied Then the visible block duration L' is never less than min(L, max(G, D)) unless prevented by allowed-time clamping And if L < max(G, D), fuzzing is suppressed (S' = S and E' = E) And changing G or D causes the fuzzed boundaries to be recomputed from the per-link seed and new config
Host-Side Preview Mirrors Recipient View
Given a host preview for a specific share link When the preview is opened with a chosen viewer timezone and date range Then the visible blocks and fuzzed edges exactly match what a recipient will see for the same link, timezone, and date range And editing fuzz settings (enable/disable, jitter range), granularity, or meeting duration updates the preview and subsequent recipient views consistently using the same seeding rules
Time Zone and DST Safe Rendering
Given a link whose blocks span a daylight saving time transition in the calendar’s base timezone T When a recipient views the link from any timezone V Then the fuzz offsets are applied in T and the resulting visible times converted to V are valid local times (no nonexistent or duplicated timestamps shown) And the same link viewed before and after the DST transition yields identical S' and E' when expressed in T for the same block date
In-Block Request and Auto-Scheduling
"As an external invitee, I want to request a time within a block without seeing exact slots so that I can schedule quickly while respecting the host’s privacy."
Description

Allow external viewers to request a meeting within a masked block without seeing exact slots. The viewer selects a block and submits a request; the system privately proposes candidate exact times to the host based on real availability, buffers, and meeting duration. Support two modes: host-approval (host picks a time from candidates) and auto-schedule (system confirms the earliest best-fit time). Place a temporary hold on candidate times to prevent double-booking until decision or timeout. Enforce per-link limits (e.g., one request per block per viewer), capture contact details, and send ICS/Calendar invites upon confirmation. Integrate with Google/Microsoft calendars, respect time zone preferences, and provide clear notifications for all parties.

Acceptance Criteria
Request Within Masked Block (Host-Approval Mode)
Given a scheduling link with masked blocks and host-approval mode enabled And a viewer selects a single masked block on date D And the meeting duration is defined by the link And the viewer provides required contact details (name and email) When the viewer submits the request Then the system creates a request with status "Pending Host Approval" And generates between 1 and 5 candidate exact start times within the selected block that respect the host’s true availability, required buffers, focus blocks, holidays, and work hours And places a temporary hold on each candidate time And the viewer sees only a confirmation referencing the block and date, not the exact times And the host receives a notification listing the candidate times and request details
Host Selects Candidate Time and Confirms
Given a pending host-approval request with candidate times on hold When the host selects one candidate time and clicks Confirm Then the system converts the selected hold into a confirmed event on the host’s connected calendar And releases all other holds for that request immediately And sends confirmation emails with ICS attachments to both host and viewer And updates the request status to "Scheduled" And the viewer is shown the confirmed exact time; prior exact times remained undisclosed before confirmation
Auto-Schedule Earliest Best-Fit
Given a scheduling link with auto-schedule mode enabled And a viewer selects a masked block and submits required contact details And at least one candidate time exists within the block meeting all constraints When the request is submitted Then the system selects the earliest-ranked candidate that satisfies duration, buffers, and work hours And immediately creates the calendar event and sends invites to both parties And returns a confirmation response to the viewer within 3 seconds including the confirmed time And updates the request status to "Scheduled" And if no candidate exists, the system returns an informative "No times available in this block" response without exposing exact times and does not create holds
Temporary Holds and Timeout
Rule: A temporary hold is created for each candidate time at request creation; hold TTL is configurable (default 15 minutes; range 5–60 minutes) Rule: Holds are represented as tentative busy events on the host’s connected calendar when supported; otherwise they are enforced internally but still excluded from Timeglue scheduling Rule: While a hold is active, candidate times are excluded from availability shown to other links and from candidate generation for concurrent requests Rule: If the host confirms one candidate before TTL, that hold converts to a confirmed event and all other holds are released Rule: If TTL expires without host action, all holds for the request are released, the request status becomes "Expired", and both host and viewer receive expiration notifications within 1 minute Rule: If the host declines, holds are released immediately and the request status becomes "Declined"
Per-Link Limits and Request Validation
Rule: A viewer (identified by email for a specific link) may have at most one active request per block; additional attempts for the same block return a validation error and do not create new holds Rule: Required fields for submission are name and email; email must be valid; missing/invalid fields prevent submission with inline errors Rule: After a request is Scheduled, Declined, or Expired, the viewer may submit a new request for that block Rule: Duplicate submissions (e.g., double-click/refresh) are de-duplicated idempotently within 60 seconds using a request identifier
Privacy Preservation for Masked Blocks
Rule: Before confirmation, the viewer UI, emails, and public APIs never reveal exact candidate timestamps; they reference only the selected block window (e.g., "Tuesday 1–3 pm") Rule: Holds and candidate times do not alter the viewer’s visible block granularity; blocks are shown as available or temporarily unavailable without showing specific times Rule: If all candidate times within a block are consumed by holds or events, the block is shown as unavailable to the viewer with no exact times displayed Rule: Host-facing notifications and internal logs may include exact times; access is restricted to authenticated host/team members
Calendar Integration, Time Zones, and Notifications
Rule: When an event is confirmed (host-approval or auto-schedule), an event is created on the host’s connected Google or Microsoft calendar with Busy transparency, correct start/end, attendees (host and viewer), title, and description including the scheduling link and request metadata Rule: Invitation emails with ICS attachments are sent to both parties within 60 seconds of confirmation; ICS specifies the correct IANA time zone and a stable UID Rule: Host-facing times are displayed in the host’s calendar time zone; viewer-facing times and emails are displayed in the viewer’s selected time zone; times remain consistent across representations within ±1 minute tolerance Rule: Integration failures are retried up to 3 times with exponential backoff; on final failure, both parties are notified and the request status becomes "Action Required" while the selected time remains held for an additional 10 minutes
Policy-Aware Block Computation
"As a host, I want masked blocks to respect my work hours, holidays, and focus blocks automatically so that I never receive after-hours or conflict-prone requests."
Description

Compute masked blocks from the host’s working hours, holidays, and focus blocks to ensure published windows never include after-hours or protected time. Merge multiple calendar sources and meeting templates (duration, buffers, lead time, max per day) to derive eligible windows, then mask according to granularity and fuzz rules. Handle region-specific holidays, partial-day events, and daylight saving changes. Provide per-link overrides for template selection and policy scope. Include caching and incremental recomputation to keep masked views responsive while staying consistent with real-time calendar changes.

Acceptance Criteria
Exclude After-Hours, Holidays, and Focus Blocks
Given a host with working hours defined per weekday in their home time zone and with designated holidays and focus blocks from all connected calendars When masked blocks are computed for a specified date range Then no published masked block overlaps any time outside working hours (0 minutes overlap) And no published masked block overlaps any holiday or focus block event, including partial-day events (0 minutes overlap) And masked blocks on days fully covered by holiday/focus events are not published
Template-Constrained Eligibility (Duration, Buffers, Lead Time, Max/Day)
Given an active meeting template with duration D minutes, buffer B minutes before and after, lead time L hours, and max M meetings per day And given existing scheduled meetings counted per day When eligible windows are derived Then only windows that can accommodate a slot of length D with buffers B on both sides are considered eligible And no masked blocks are published on any day where existing scheduled meetings >= M And no masked block includes any time starting sooner than L hours from now And every published masked block contains at least one bookable slot that satisfies D and B at computation time
Granularity Masking and Fuzzed Edges
Given a masking granularity G (e.g., 60, 120, or 180 minutes) and fuzz settings Fmin..Fmax minutes with fuzz enabled When eligible windows are masked for publication Then block start/end times are snapped to multiples of G within the eligible window And each boundary is offset by a random value within [Fmin, Fmax] minutes and clamped to the eligible window when fuzz is enabled And masked boundaries never extend beyond eligible windows and never reveal exact slot boundaries And when fuzz is disabled, boundaries are snapped to G without additional offset And the public response contains only masked intervals; no exact slot timestamps are present
Time Zone, Holidays by Region, and DST-Safe Computation
Given the host’s region-specific holiday calendar and IANA time zone And a date range that includes daylight saving transitions (spring-forward and fall-back) When masked blocks are computed Then holidays marked for the host’s region are excluded; holidays from other regions are ignored unless explicitly included And on spring-forward days, nonexistent local times are not published; masked blocks are adjusted to actual wall-clock hours And on fall-back days, overlapping repeated-hour intervals are handled without duplicating availability; masked blocks remain continuous in local wall time And masked blocks are returned with correct UTC offsets per day
Per-Link Overrides for Template and Policy Scope
Given two share links for the same host: L1 with default policy and L2 with overrides (e.g., alternate template, ignore focus blocks toggle, different work-hours scope) When each link is fetched Then masked blocks for L2 reflect its overrides and differ from L1 where policies diverge And overrides take precedence over account defaults for that link only And changes to overrides on L2 do not alter results for L1 And audit logs record the override set used per computation
Caching and Incremental Recomputation Responsiveness
Given cached masked blocks for a 14-day horizon And an input change occurs (e.g., a new focus block added, a meeting created, or work hours updated) When recomputation is triggered by webhook or polling Then only affected days/blocks are recomputed and cache entries for unaffected days remain valid And the public masked view reflects the change within 5 seconds of receipt of the change event at P95 (10 seconds max) And concurrent requests during recomputation return either the previous consistent view or the updated consistent view; no mixed state is returned And cache TTL, version, and last-computed timestamps are included in responses
Multi-Calendar Source Merge and Conflict Resolution
Given the host has multiple connected calendars (e.g., Google and Outlook) with overlapping events, all-day events, and partial-day busy blocks When eligible windows are computed Then the union of busy times across all sources is excluded from eligibility (0 minutes overlap) And all-day events marked as free do not block; all-day events marked as busy fully block the day; partial-day all-day events block only their time span And event transparency and response status are respected according to policy (only busy/accepted/tentative events block) And duplicate events across sources are de-duplicated using UID and time heuristics to avoid double blocking
Secure Share Links with Access Controls
"As a host, I want to protect share links with expiration and access rules so that only intended recipients can request time within a limited period."
Description

Generate shareable masked-availability links with configurable access controls: optional passcode, allowed email domains, single-use tokens, expiration dates, and maximum views/requests. Support link revocation and regeneration without changing underlying settings. Allow per-link customization of granularity, fuzzing, and meeting template. Log access events for audit and provide simple management UI to view, pause, or expire links. Ensure links are unguessable, short, and compatible with common sharing channels.

Acceptance Criteria
Passcode-Protected Link Access
Given a share link with passcode protection enabled When an external viewer opens the link Then the viewer is prompted to enter the passcode before any availability is displayed And when the correct passcode is entered, access is granted and masked availability blocks are shown And when an incorrect passcode is entered, access is denied with a generic error and no availability or metadata is revealed And when passcode protection is toggled off by the owner, subsequent accesses no longer require a passcode
Allowed Email Domains Enforcement
Given a share link configured with allowed email domains ["example.com", "agency.co"] When a viewer provides an email address to proceed Then access is granted only if the email’s domain matches one of the allowed domains (case-insensitive, whitespace-trimmed) And addresses from other domains are rejected with the message "This link is restricted to specific email domains" and no availability is shown And no partial availability, titles, or times are revealed prior to successful domain validation
Expiration and Max Views/Requests Limits (incl. Single-Use)
Given a share link configured with an expiration date/time, a maximum views limit, and a maximum requests limit When a viewer attempts to access after the expiration time Then access is denied with the message "Link expired" and the event is logged And when availability is successfully displayed, the view counter increments; upon reaching the max views, further attempts are blocked with "View limit reached" And when a meeting request is successfully submitted, the request counter increments; upon reaching the max requests, further attempts are blocked with "Request limit reached" And when single-use mode is enabled (max requests = 1), the link becomes permanently consumed immediately after the first successful request submission
Link Revocation and Regeneration
Given an active share link When the owner selects "Revoke" Then the link becomes immediately invalid; subsequent access attempts return "Link revoked" and are logged And when the owner selects "Regenerate link" for the same configuration Then a new, different URL is created with identical access-control, masking, and template settings And the prior URL remains invalid, and underlying availability and template settings are unchanged by revocation or regeneration
Per-Link Masking and Meeting Template Overrides
Given the link configuration screen When the owner sets the masking granularity to 1–3 hour bands and optionally enables fuzzed edges (e.g., 5–30 minutes) Then the public availability for that link is displayed using the selected band size and fuzzing And displayed block boundaries do not reveal exact underlying start/end times within the configured fuzz window And when the owner selects a meeting template (duration, location, buffers), requests created via the link use that template And changing these per-link settings does not alter the owner’s global defaults
Access Log and Link Management UI
Given the owner opens the Links management screen When viewing a specific link Then the UI displays: link title, status (active/paused/expired/revoked), created date, expiration, views used/max, requests used/max, and last access time And the owner can pause/unpause and expire-now the link; changes take effect within 5 seconds And an access log shows entries with timestamp (UTC), outcome (granted/denied), reason (e.g., passcode_failed, domain_denied, expired, revoked, limit_reached), and request ID if created And log entries are retained for at least 90 days and can be exported as CSV
Link Format: Unguessable, Short, and Shareable
Given a newly generated share link When inspecting the URL Then the token contains at least 128 bits of entropy and uses URL-safe characters only And the full URL length is ≤ 80 characters and the token segment is ≤ 22 characters And the URL contains no personally identifiable information And when pasted into Slack, Gmail, Outlook, and Teams, it renders as a clickable link with no private availability content in link previews/unfurls
Privacy Hardening and Anti-Fingerprinting
"As a security-conscious admin, I want the masked view to avoid leaking scheduling details via UI or API side channels so that our team’s availability remains private."
Description

Ensure no exact availability is leaked through side channels in the masked view. Standardize response sizes and timing for availability endpoints, avoid slot-count-dependent UI skeletons, and rate-limit probing behavior. Obfuscate internal error details, and ensure analytics and logs store only band-level signals. Provide configurable thresholds to collapse narrow gaps that could reveal precise slots. Include privacy review, automated tests for leakage scenarios, and documentation of data processing for compliance.

Acceptance Criteria
Standardized Response Size and Timing in Masked Availability API
- Given a masked availability link with any underlying schedule, When GET /api/availability?masked=true is requested, Then the HTTP response Content-Length is exactly 20,480 bytes for 1-hour bands, 16,384 bytes for 2-hour bands, or 12,288 bytes for 3-hour bands, determined solely by the public band size configuration. - And across 500 requests on empty vs. dense calendars, the P95 end-to-end latency lies between 350 ms and 450 ms for both cases, and the P99 latency difference between the two cases is ≤ 50 ms. - And responses include Cache-Control: no-store and omit Server-Timing and similar per-request timing headers.
Slot-Count-Agnostic Loading Skeleton
- Given masked availability is loading, When rendering the day view, Then the UI displays a fixed skeleton of 24/band_size blocks per day regardless of underlying available slots. - And the DOM node count of the skeleton differs by ≤ 2 nodes across any availability state. - And no visible text or aria-labels reveal counts of available slots. - And the network waterfall contains exactly one availability request with no conditional sub-requests based on slot count.
Rate Limiting and Probe Throttling
- Given an external viewer or IP, When more than 30 masked availability requests are made within 60 seconds, Then subsequent requests within that window return HTTP 429 with Retry-After: 60. - And if ≥ 5 successive requests vary startAt by < 5 minutes within 60 seconds, Then rate limiting is triggered as above. - And after 3 rate-limit events within 10 minutes, Then the source is blocked for 10 minutes with a uniform 429 body of exactly 1,024 bytes. - And band boundaries in responses do not change while rate limited.
Obfuscated Error Responses
- Given any server-side error on a masked availability endpoint, When an error occurs, Then the response body contains only {code, message, correlation_id}, with message = "Temporarily unavailable" and code from an allowlist, and no stack traces, filenames, SQL, or internal service names. - And the error response body is padded to exactly 2,048 bytes. - And server logs for such errors record only correlation_id, HTTP status, and endpoint name; request bodies and exact event timestamps are not logged.
Band-Level Analytics and Log Redaction
- Given analytics/logging for masked availability, When events are written, Then they include only band_size, band_start_index, band_count, fuzzed_edges flag, and hashed viewer_id; no exact timestamps or timezone-offset minutes are stored. - And a daily automated scan finds 0 events containing ISO 8601 timestamps with minute precision. - And IP addresses, if stored, are truncated to /24 (IPv4) or /56 (IPv6) prefix. - And masked analytics retention is 30 days with automated purge verified by a scheduled job success log.
Configurable Gap Collapse Thresholds
- Given admin config gapCollapseThresholdMin ∈ {10,15,20,30}, When set, Then any gaps strictly less than the threshold are merged into adjacent bands in masked output; gaps equal to the threshold are not merged. - And the default threshold is 20 minutes. - And configuration changes propagate and are reflected in new masked availability responses within 60 seconds.
Leakage Test Suite and Privacy Review Sign-off
- Given the CI pipeline, When leakage tests run, Then they pass checks for constant-size responses (per band size), P99 latency delta ≤ 50 ms between empty vs. dense calendars, stable skeleton DOM node count, and absence of timing headers. - And adversarial probe simulations (≥ 10,000 requests) cannot infer precise slot edges with accuracy greater than random baseline + 5%. - And a DPIA, data-flow diagram, and ROPA entry are completed and approved, with documents stored and versioned; the feature is not enabled without recorded approval.

Ephemeral Feeds

Generate per-link ICS feeds that auto-expire by time or view count, rotate tokens on a schedule, and support one-click revocation. Optional domain/IP binding thwarts scraping, ensuring partners see only current, minimal data without lingering access.

Requirements

Per-Link ICS Feed Generation
"As a remote team lead, I want a unique ICS feed per shared link so that partners only see the availability relevant to that link without exposing my broader calendar."
Description

Create unique ICS calendar feeds per shared scheduling link, scoped to the specific timeline and filters defined by the link owner. Each feed encapsulates only the minimal, current availability/events necessary to schedule, excluding unrelated calendars or historical data. The system must generate feeds on demand with signed, opaque tokens, cache responses efficiently, and maintain compatibility with major calendar clients (Google, Microsoft, Apple). This integrates with Timeglue’s availability engine to render only compliant time windows and respect work hours, holidays, and focus blocks.

Acceptance Criteria
Per-Link Scoped ICS Feed Generation and Minimal Data Exposure
Given a scheduling link with a defined timeline, region filters, and availability rules When an ICS feed is generated for that link Then the ICS contains only events/windows within the link’s timeline and filters And no events have a DTSTART earlier than the current time And no events from unrelated calendars or links are included And each VEVENT uses SUMMARY="Available" and includes only UID, DTSTART, DTEND (with TZID as needed), DTSTAMP; no ATTENDEE, ORGANIZER, EMAIL, LOCATION, or custom properties are present And the feed reflects Timeglue’s availability engine results, honoring work hours, holidays, and focus blocks
Time-Based and View-Count Expiration
Given a feed configured with expires_at = T+30m and max_views = 3 When the feed is requested successfully three times before T+30m Then the fourth request returns 410 Gone with error_code="expired_view_limit" and Cache-Control: no-store And any request at or after T+30m returns 410 Gone with error_code="expired_time" and Cache-Control: no-store And only HTTP 200 responses count toward max_views; 304, 401, 403, and 410 responses do not increment the counter And an audit event is recorded for each expiration with reason and timestamp
Token Security and Scheduled Rotation
Given a per-link ICS feed URL is issued Then the URL contains a signed, opaque token of at least 32 base64url characters with no embedded PII And requests missing, tampered, or malformed tokens return 403 with error_code="invalid_token" And HTTPS is required; HTTP requests are rejected with 400 or redirected to HTTPS Given rotation_interval = 24h and grace_period = 2h When the rotation job runs Then a new valid token is issued and accepted immediately And the previous token continues to work for the 2h grace period And after the grace period, the previous token returns 401 with error_code="token_expired" And all rotations are logged with old_token_id and new_token_id references
One-Click Revocation
Given a feed is active and being polled by clients When the owner clicks "Revoke" in the UI or calls the revoke API Then subsequent requests for that feed return 410 Gone within 60 seconds And server responses include Cache-Control: no-store and omit ETag/Last-Modified And any server-side cached variants for the feed are invalidated immediately And an audit event is recorded including actor, feed_id, and reason And generating a new feed for the same link produces a different token/URL
Optional Domain/IP Binding Enforcement
Given a feed is configured with allowed_host = partner.example.com and allowed_cidrs = ["203.0.113.0/24"] When a request presents Host: partner.example.com and originates from 203.0.113.10 Then the server returns 200 with the ICS content When a request presents a different Host or originates outside the allowed CIDRs Then the server returns 403 with error_code="binding_mismatch" And binding checks support both IPv4 and IPv6 CIDRs and are case-insensitive for the Host header And disabling binding returns the feed to default access (token-only) immediately
Caching, ETags, and Conditional Requests
Given the first GET for a feed returns 200 with ETag="E1" and Cache-Control: max-age=300 When the client sends If-None-Match: E1 within 300 seconds and the feed has not changed Then the server returns 304 Not Modified When the underlying availability or link configuration changes Then the next GET returns 200 with updated content and a new ETag="E2" And after revocation or expiry, responses are 410 with Cache-Control: no-store and no ETag And under a cache-hit, p95 response time is <= 300ms; under a cache-miss, p95 is <= 800ms in staging with 50 concurrent requests
Cross-Client Compatibility (Google, Microsoft, Apple)
Given the ICS URL is subscribed in Google Calendar (web), Microsoft 365/Outlook, and Apple Calendar Then each client accepts the URL without errors and displays events in the correct timezone And subsequent updates to availability reflect in all clients within their normal refresh windows without duplicating events (stable UID semantics) And the feed passes iCalendar validation (RFC 5545) with no errors at critical severity And DTSTART/DTEND timezone rendering is consistent across clients (no off-by-one-hour during DST transitions)
Configurable Expiration Policies
"As a security-conscious manager, I want feeds to auto-expire by date or views so that temporary partners lose access automatically without manual cleanup."
Description

Support time-based and view-count-based auto-expiration for each per-link ICS feed. Link owners can set a precise expiry date/time, a relative TTL (e.g., 7 days), and/or a maximum fetch count. Upon expiry, the feed returns a well-formed, no-data ICS with an explanatory X-STATUS field. Provide UI and API controls to configure, preview, and update policies, plus guardrails (min/max values) and default expirations. Integrates with background jobs to invalidate caches and with audit logs for traceability.

Acceptance Criteria
Set Absolute Expiry Date/Time in UI
Given a feed exists and the owner opens its Expiration Policy UI And system guardrails are configured as MinTTL=1h, MaxTTL=90d, DefaultTTL=7d, MaxFetch=500 When the owner sets Absolute Expiry to a timestamp within [now+MinTTL, now+MaxTTL] and saves Then the policy is persisted and the feed’s computed expiresAt equals the selected timestamp (to the minute) And the UI displays the expiry in the owner’s timezone and UTC And the Preview shows active state for times before expiresAt and expired state for times after expiresAt
Configure Relative TTL via API
Given a feed exists and no absolute expiry is set When the client PUTs /feeds/{id}/policy with body {"ttl":"P7D"} within guardrails Then HTTP 200 is returned with body including {"ttl":"P7D","expiresAt":"<ISO-8601 UTC>"} And expiresAt equals now+7 days ±1 second When the client sets ttl below MinTTL or above MaxTTL Then HTTP 400 is returned with code "TTL_OUT_OF_RANGE" and a message including allowed bounds
Max Fetch Count Expiration and Post-Expiry ICS
Given a feed has maxFetch=3 and is otherwise active When the ICS URL is fetched 3 times successfully Then the 3rd response returns the normal event data and decrements remaining to 0 atomically When the ICS URL is fetched a 4th time Then HTTP 200 with Content-Type text/calendar is returned containing a well-formed VCALENDAR with zero VEVENTs And the calendar includes X-STATUS: EXPIRED;reason=fetch-limit And the response passes iCalendar validation and contains no sensitive fields And concurrent requests do not allow total successful fetches to exceed maxFetch
Combined Policies — Earliest-Wins Precedence
Given Absolute Expiry T1, TTL T2 (computed to T2'), and maxFetch N are all set When any of T1 is reached, remainingFetches reaches 0, or current time passes T2' Then the feed transitions to expired and subsequent requests return the no-data ICS with X-STATUS explaining the cause And X-STATUS reason is "time-expired" if T1 or T2' caused expiry, or "fetch-limit" if N caused expiry And the audit log records expirationCause matching the first-triggered condition
Guardrails, Defaults, and Error Messaging
Given system guardrails MinTTL=1h, MaxTTL=90d, DefaultExpiry=14d, and MaxFetch range [1,5000] When a new feed is created with no explicit policy Then DefaultExpiry is applied and surfaced as ttl and expiresAt in UI and API When a user attempts to set ttl or maxFetch outside guardrails via UI or API Then the save is rejected with HTTP 400 (API) or inline error (UI) using codes TTL_OUT_OF_RANGE or MAXFETCH_OUT_OF_RANGE and including allowed bounds And no partial updates are committed when any field is invalid
Background Expiry and Cache Invalidation
Given a feed has expiresAt=T and its ICS responses are cached (app and CDN) When time reaches T Then a background job marks the feed expired and purges related caches within 60 seconds And the first request after T returns the expired no-data ICS with a new ETag And cached pre-expiry responses are not served beyond T+60s
Audit Logging and Traceability
Given a user or API client creates, updates, or deletes an expiration policy When the action is committed Then an audit entry is recorded with feedId, actorId, actorType (user|api), ip/domain, timestamp, requestId, and old→new diff of policy fields When a feed expires by time or fetch limit Then an audit entry records expirationCause, expiresAt, and remainingFetches at expiry And audit entries are retrievable via GET /audit?feedId= with pagination and filters for action type and date range
Scheduled Token Rotation
"As an IT admin, I want tokens to rotate on a schedule so that long-lived links reduce risk if a token leaks."
Description

Enable automatic rotation of feed access tokens on a configurable schedule (e.g., every 24 hours) without changing the public feed URL structure visible to recipients. Implement dual-token grace periods to avoid client breakage during rotation and ensure backward compatibility for a short window. Provide admin settings, rotation cadence presets, and webhook events for rotations. Integrate with key management, cache invalidation, and monitoring to detect failed rotations.

Acceptance Criteria
Automatic Rotation Executes on Schedule
Given a feed with rotation cadence set to 24h and a next-rotation timestamp T in UTC When T is reached Then a new access token is generated and activated within 60 seconds And the next rotation is scheduled for T+24h And the operation is idempotent if triggered multiple times within the same minute
Stable Public Feed URL During Rotation
Given a recipient has an existing ICS feed URL for a specific feed When a scheduled token rotation occurs Then the exact feed URL string remains unchanged before, during, and after rotation And requests to the unchanged URL succeed (HTTP 200) during the grace window and after with the new token And no client-side action is required to maintain access
Dual-Token Grace Period Backward Compatibility
Given a rotation occurs at time T with a configured grace period G (default 2 hours) When requests are made between T and T+G Then both the previous token and the new token are accepted And after T+G, the previous token is rejected with HTTP 401 and an explicit error code token_expired And audit logs record the exact accept/reject decision with token_id and timestamp
Admin Configurable Rotation Settings and Presets
Given an admin opens Feed Security settings When they configure rotation cadence using presets (6h, 12h, 24h, 7d) or a custom value (min 1h, max 30d) Then the setting is validated, saved, and displayed consistently in UI and API And the grace period is configurable (min 15m, max 24h) with inline validation And settings changes take effect on the next scheduled rotation and are captured in an audit trail with actor, previous_value, new_value, and timestamp
Webhook Events Emitted on Rotation
Given webhooks are configured for the workspace When a rotation starts and completes Then rotation.started and rotation.completed events are delivered within 30 seconds And the payload includes feed_id, old_token_id (hashed), new_token_id (hashed), occurred_at, next_rotation_at, and grace_period_seconds And delivery is signed, retried up to 6 times with exponential backoff, and is idempotent via event_id
Key Management and Cache Invalidation
Given a rotation is executing When the new token is created Then it is stored atomically and the previous token is marked as deprecating And application and CDN caches referencing token-to-feed mappings are invalidated within 60 seconds And no requests succeed with the previous token after the grace period due to stale cache
Monitoring, Retry, and Alerting on Failed Rotations
Given a scheduled rotation fails at any step When the failure is detected Then the system retries up to 3 times with exponential backoff And if still failing, the previous token remains valid and the rotation is marked failed without client disruption And an alert is sent to configured channels within 5 minutes containing feed_id and error details, and metrics (attempts, successes, failures, duration p95) are recorded
One-Click Revocation & Kill Switch
"As a project lead, I want to revoke feed access with one click so that I can instantly stop data exposure when a partnership ends or a leak is suspected."
Description

Provide an immediate, single-action control to revoke an individual feed or all feeds associated with a link/owner. Revocation invalidates tokens, clears caches, and forces subsequent requests to return an expired/no-access ICS response. Include confirmation prompts, undo within a brief window, and audit logging with actor, timestamp, and reason. Expose revocation via UI and API for automation workflows.

Acceptance Criteria
Single Feed Revocation via UI
Given an active ICS feed is visible in the owner’s dashboard When the owner clicks Revoke and confirms Then the feed’s token is invalidated within 5 seconds And subsequent GET requests to the feed URL return the expired/no-access ICS response And the UI marks the feed as Revoked with timestamp and actor Given a feed is already revoked When the owner attempts to revoke it again Then the system shows Already revoked and no additional side effects occur
Owner-Level Kill Switch via UI
Given an owner has N active feeds (N ≥ 1) When the owner activates the Kill Switch and confirms Then all N feeds are revoked within 10 seconds And subsequent requests to any of those feed URLs return the expired/no-access ICS response And the UI displays a success summary including the count revoked Given an owner has zero active feeds When the owner activates the Kill Switch Then the system shows No active feeds to revoke and takes no action
API Revocation with Idempotency and Undo
Given a valid access token with scope feeds:write and an active feed ID When a POST /v1/feeds/{feedId}/revoke is sent with an Idempotency-Key header Then the API returns 200 with JSON including revocationId and status=revoked within 5 seconds And retrying the same request with the same Idempotency-Key within 24 hours returns 200 with the same revocationId and no duplicate side effects Given a valid revocationId within 60 seconds of the revocation When a POST /v1/revocations/{revocationId}/undo is sent Then the feed is restored within 5 seconds And subsequent GET requests to the feed URL return a normal ICS response (HTTP 200) Given a revocation older than 60 seconds When a POST /v1/revocations/{revocationId}/undo is sent Then the API returns 409 Conflict and the feed remains revoked Given an owner with active feeds When a POST /v1/owners/{ownerId}/feeds/revoke is sent Then the API returns 200 with the count of feeds revoked and a batch revocationId
Expired/No-Access ICS Response Contract
Given a revoked feed When a client requests the feed URL Then the server responds with HTTP 410 Gone And Content-Type is text/calendar; charset=utf-8 And the body is a VCALENDAR with zero VEVENT components And includes PRODID:-//Timeglue//ICS//EN and X-TIMEGLUE-STATUS: REVOKED And response headers include Cache-Control: no-store, no-cache, must-revalidate and Expires: 0 Given Accept header is */* or text/calendar When requesting a revoked feed Then the same 410 and ICS body are returned
Token and Cache Invalidation Propagation SLA
Given a feed is revoked (single or via kill switch) When probing the feed URL from three regions (NA, EU, APAC) every second Then 95th percentile time to first expired/no-access ICS response across regions is ≤ 10 seconds from revocation And 99th percentile is ≤ 20 seconds Given CDN/app caches previously served the feed When the feed is revoked Then all cache layers stop serving the pre-revocation content within 10 seconds And revoked responses are not cached (Cache-Control: no-store)
Confirmation Prompt and Undo Window in UI
Given the user initiates Revoke on a single feed or activates the Kill Switch When the confirmation modal opens Then it displays the target identifier/scope, impact summary (count of feeds), and a required Reason input (3–200 characters) And the Confirm button is disabled until validation passes Given the user confirms with a valid Reason When revocation completes Then a success toast shows Undo with a visible countdown starting at 60 seconds And clicking Undo within the countdown restores the prior state within 5 seconds And the toast changes to Restored Given the countdown expires When Undo is clicked after expiry Then the UI shows Undo window expired and no restoration occurs
Audit Logging for Revocation and Undo
Given any revocation or undo action via UI or API When the action completes Then an immutable audit record is created with fields: actorId or clientId, action (revoke|undo), targetType (feed|owner), targetId, timestamp (UTC ISO-8601), reason (for revoke), revocationId/batchId, and correlationId And no secret material (e.g., feed tokens) is present in logs Given an authorized user with audit:read permission When calling GET /v1/audit?targetId={id}&targetType=feed&page=1&pageSize=50 Then the API returns 200 within 2 seconds with paginated, chronologically ordered records And records are tamper-evident (include a hash or signature field)
Domain/IP Binding & Request Validation
"As a compliance officer, I want feeds restricted to specific domains or IPs so that only authorized partners can fetch data from approved environments."
Description

Allow optional binding of a feed to approved referrer domains and/or client IP ranges to mitigate scraping and unauthorized redistribution. Implement configurable allowlists/denylists, rate limiting, and signature-based request validation using HMAC headers. Provide clear failure responses and diagnostics to assist legitimate partners in correcting configuration. Ensure privacy-preserving logging and compliance with regional data laws.

Acceptance Criteria
Domain Allowlist — Valid Referrer Served
Given a feed has domain binding enabled with an allowlist ["app.example.com", "*.partner.co"] and an empty denylist When a GET request to the feed URL includes Referer: https://app.example.com/path Then the server responds 200 OK with Content-Type: text/calendar And the ICS body is non-empty And the response includes header X-Timeglue-Request-Id And no error fields are present in the response
IP CIDR Enforcement — Out-of-Range Client Blocked
Given IP binding is enabled with an allowlist ["203.0.113.0/24"] and no domain binding When a client at IP 198.51.100.10 requests the feed URL Then the server responds 403 Forbidden with Content-Type: application/json And the body includes error.code = "ip_not_allowed" and a human-readable error.message and hint And the response includes header X-Timeglue-Request-Id And no ICS content is returned
HMAC Request Validation — Valid Signature and Fresh Timestamp
Given HMAC validation is enabled with HMAC-SHA256 and a shared key K And the client sends headers X-Timeglue-Signature, X-Timeglue-Timestamp (within ±300s of server time), and X-Timeglue-Nonce (previously unused) And the signature is computed over method + "\n" + path + "\n" + timestamp + "\n" + nonce using K When the request is received Then the server verifies the signature, timestamp, and nonce uniqueness And responds 200 OK with Content-Type: text/calendar And records the nonce as used for at least 10 minutes to prevent replay
HMAC Request Validation — Invalid or Expired Signature Rejected
Given HMAC validation is enabled When the X-Timeglue-Signature header is missing Then the server responds 401 Unauthorized with JSON body error.code = "signature_required" When the X-Timeglue-Timestamp is older/newer than ±300s of server time Then the server responds 401 Unauthorized with JSON body error.code = "signature_expired" and includes a Date header When the signature does not match the computed value Then the server responds 401 Unauthorized with JSON body error.code = "signature_invalid" And in all cases the response includes X-Timeglue-Request-Id and contains no ICS content
Rate Limiting — Per-Feed Throttle with Retry-After
Given a per-feed rate limit of 60 requests per 600 seconds per client IP is configured When a client makes 61 requests within 600 seconds to the same feed Then the 61st response is 429 Too Many Requests with headers RateLimit-Limit: 60;window=600, RateLimit-Remaining: 0, and Retry-After present And the response body is JSON with error.code = "rate_limited" and includes seconds-until-reset or a timestamp in the hint And a request made after the window resets succeeds with 200 OK
Privacy-Preserving Logging — Minimal Retention and Redaction
Given request logging is enabled and the feed is served to an EU-resident tenant When any request (success or failure) is processed Then logs do not store full client IP; IPv4 is truncated to /24 and IPv6 to /56 (or an irreversible hash is stored) And logs store only the referrer host; no path or query parameters are persisted And request/response bodies (including ICS payload) are not logged And logs for the EU tenant are written to EU-resident storage And access logs older than 30 days are automatically purged
Diagnostics — Standardized Failure Schema and Self-Service Hints
Given a request fails due to referrer, IP, HMAC, or rate limiting rules When the server returns an error response Then Content-Type is application/json and the body contains fields: code (string), message (string), hint (string), request_id (string), docs_url (URL) And the same request_id is present in the X-Timeglue-Request-Id response header And the body contains no secrets (e.g., keys, token hashes) and no full client IP; only truncated/redacted values if included
Least-Data Exposure Controls
"As a team lead, I want to control what fields and time range a feed exposes so that partners see only what’s necessary to schedule with me."
Description

Let link owners select exactly which fields appear in the ICS (e.g., busy/free only, event summaries redacted, organizer hidden) and limit time horizons (e.g., next 14 days). Defaults favor minimal disclosure while preserving scheduling utility. Implement server-side enforcement, schema validation, and per-link templates. Ensure that changes are reflected immediately in subsequent fetches and documented via inline ICS properties (e.g., X-PRIVACY).

Acceptance Criteria
Busy/Free Only ICS Feed
Given a per-link template set to "Busy/Free only" with minimal fields When the ICS feed is fetched Then each VEVENT contains only UID, DTSTAMP, DTSTART, DTEND (or DURATION), SEQUENCE, TRANSP and an optional constant SUMMARY value of "Busy" And no VEVENT contains DESCRIPTION, LOCATION, ORGANIZER, ATTENDEE, URL, CONFERENCE, or any custom fields other than allowed X-PRIVACY* And the total number of VEVENTs equals the number of source events within the time horizon And any attempt to request additional fields via query parameters or headers is ignored and does not change the returned properties
Redacted Summaries and Organizer Hidden
Given a per-link template configured with "Redact summaries" and "Hide organizer" When the ICS feed is fetched Then every VEVENT has SUMMARY replaced with a configured redaction token (e.g., "[REDACTED]") And no VEVENT includes an ORGANIZER property And ATTENDEE properties are omitted unless explicitly whitelisted in the template And no original SUMMARY text is present anywhere in the ICS (including DESCRIPTION, COMMENT, or X-* fields)
14-Day Forward Time Horizon Enforcement
Given a per-link template with a time horizon of the next 14 days When the ICS feed is fetched at time T0 Then all included events have DTSTART >= T0 and < T0 + 14 days (evaluated in absolute time regardless of TZID) And events with DTSTART before T0 are excluded even if they end after T0 And events with DTSTART on or after T0 + 14 days are excluded And if a client supplies range parameters that exceed the horizon, the server clamps results to the configured horizon without error
Immediate Privacy Setting Propagation on Fetch
Given an existing link initially allowing ORGANIZER and non-redacted SUMMARY And the owner updates the template to hide ORGANIZER and redact SUMMARY at time Tchange When any client fetches the ICS at or after Tchange Then the response reflects the new template on the first fetch after Tchange And the response ETag (or equivalent version identifier) changes from the previous version And downstream caches are controlled such that no client observes stale data more than 5 seconds after Tchange (Cache-Control or equivalent enforced)
Per-Link Template Isolation and Enforcement
Given two different links A and B pointing to the same calendar data with distinct privacy templates When the ICS for link A is fetched Then it adheres strictly to template A and does not leak any fields only allowed by template B And when the ICS for link B is fetched it adheres strictly to template B And changing template A does not alter the output of link B and vice versa
Template Schema Validation and Rejection of Unsafe Fields
Given a request to save a per-link template containing unknown fields, disallowed properties, or invalid values When the template is validated server-side Then the server rejects the template with HTTP 422 including a machine-readable error list (field, issue, expected) And no partial or previous template is applied or changed on failure And on success, only properties explicitly allowed by the schema are persisted And ICS generation for any saved template passes RFC 5545 validation with zero errors
Inline X-PRIVACY Properties Accuracy
Given any ICS generated under a per-link privacy template When the ICS is fetched Then the calendar includes X-PRIVACY-MODE, X-PRIVACY-FIELDS, X-PRIVACY-HORIZON, and X-PRIVACY-VERSION properties at the VCALENDAR level And their values accurately describe the applied template (e.g., fields enumerated match actual emitted properties; horizon value matches configured limit) And a change to the template updates these X-PRIVACY* values on the next fetch
Access Analytics & Alerts
"As a link owner, I want visibility and alerts on feed access so that I can detect misuse and respond before it becomes a problem."
Description

Track anonymized access metrics per feed (last fetched, fetch count, unique IPs/domains, geos) and surface trends for anomaly detection (sudden spikes, out-of-geo access). Provide configurable alerts (email, Slack, webhook) for suspicious patterns, upcoming expirations, or failed rotations. Integrate with existing observability stack and respect opt-out and data retention policies.

Acceptance Criteria
Per-Feed Anonymized Access Metrics Capture
Given an active Ephemeral Feed is fetched via its ICS URL by any client When the request completes successfully Then last_fetched_at for that feed is updated within 60 seconds of the request finish time And total_fetch_count increments by 1 And unique_ip_count updates using a daily-rotating salted hash of the source IP within the retention window And unique_domain_count updates using the requesting domain when available (TLS SNI or HTTP Host), de-duplicated within the retention window And country_code and region_code are derived from IP and stored (no city or latitude/longitude) And no raw IP address is stored anywhere; only the salted hash is persisted And the updated metrics are available via API and dashboard within 2 minutes
Spike Detection and Trend Surfacing
Given baseline is the 7-day moving average of hourly fetch counts for the feed When the current 60-minute fetch count exceeds 3x the baseline and by at least 50 fetches, sustained for 10 minutes Then the feed is marked with a Spike status in the dashboard and an anomaly event is recorded And the trend chart displays baseline vs current with the spike interval highlighted And the Spike status auto-clears after 60 minutes below 1.2x baseline and a recovery event is recorded
Out-of-Geo Access Anomaly Detection
Given a feed has either an explicit allowlist of ISO country codes or an inferred baseline of observed countries over the last 30 days When a fetch originates from a country not in the allowlist or not observed in the last 30 days and accounts for at least 10 fetches or 5% of today’s fetches (whichever is greater) Then an Out-of-Geo anomaly is created with country, first_seen_at, and count, and the dashboard flags the feed And no sub-country geolocation is stored or displayed And duplicate anomalies for the same feed-country are suppressed for 24 hours
Configurable Alerts for Suspicious Activity, Expirations, and Rotation Failures
Given alert channels (email, Slack, webhook) and rules are configured at the feed or org level When a spike anomaly, out-of-geo anomaly, upcoming expiration threshold (e.g., T−3 days), or token rotation failure occurs Then an alert is sent on all enabled channels within 2 minutes of detection And alerts include feed_id, trigger_type, severity, first_seen_at, sample metrics, and a dashboard link And per-trigger cooldown prevents duplicate alerts for 60 minutes per feed And webhook deliveries are HMAC-SHA256 signed, retried up to 3 times with exponential backoff, and marked failed after 15 minutes And Slack and email alerts indicate delivery status; failed deliveries are retried twice
Opt-Out Compliance and Data Retention Enforcement
Given analytics collection is configurable per organization and per feed When analytics is opted out at the org or feed level Then no new access metrics or anomalies are collected for the opted-out scope And all previously stored analytics for that scope are purged within 24 hours And alert rules for that scope are disabled and no alerts are sent And a configurable retention window (e.g., 30/90 days) deletes analytics older than the window daily at 02:00 UTC And API responses and UI clearly indicate analytics is disabled for the scope
Observability Integration and Privacy Guardrails
Given the observability integration is enabled with an OpenTelemetry endpoint for metrics, logs, and traces When analytics events and alerts are produced Then corresponding telemetry is exported with feed_id, org_id, trigger_type, and geo tags within 60 seconds for 99% of events And no raw IPs or email addresses are exported; only hashed IPs and ISO country codes And metric/tag cardinality is constrained (e.g., max 100 distinct countries per day); excess values are bucketed as other And export failures are retried for up to 10 minutes with backoff; if unsent, an internal alert is raised without exposing customer data

Trust Tiers

Set graduated visibility based on recipient identity. Anonymous viewers see day-level open/closed; verified partners via SSO/OAuth see hour bands; allowlisted contacts can view narrower windows. Share the least necessary detail to move work forward safely.

Requirements

Tiered Identity & Session Detection
"As a link recipient, I want to be recognized and assigned a trust tier automatically so that I see only the necessary scheduling detail."
Description

Implement multi-tier recipient identification for shared Timeglue links using anonymous sessions, SSO/OAuth verification (Google, Microsoft, Okta), and allowlist token matching. On link open, determine identity and assign a trust tier without requiring a Timeglue account via signed link tokens and optional SSO handoff, persisting a short-lived session and re-evaluating on each visit. Provide secure tokenization (HMAC/rotating keys), anti-replay, and domain-based mapping for partners. Integrate with the existing smart link generator, workspace auth settings, and invite flows, exposing API/webhook events for verification state changes. Outcome: recipients are reliably mapped to the correct trust tier for least-privilege visibility across web, embeds, and calendar integrations.

Acceptance Criteria
Anonymous Session Assigned to Baseline Trust Tier on First Link Open
- Given a recipient opens a shared link without a valid signed token and without completing SSO/OAuth, When the link loads, Then a short-lived anonymous session is created and the lowest trust tier is assigned, displaying only day-level open/closed. - Then the assigned tier is enforced consistently in web views, embedded widgets, and calendar (ICS/API) responses. - And if the workspace auth setting is "SSO required," When the link loads, Then the recipient is redirected to SSO and no scheduling data beyond day-level open/closed is shown until verification succeeds. - And the anonymous session cookie is HttpOnly, Secure, SameSite=Lax, with idle timeout <= 30 minutes and absolute lifetime <= 24 hours.
SSO/OAuth Verification Elevates Trust Tier Without Timeglue Account
- Given the recipient initiates SSO/OAuth with Google, Microsoft, or Okta from a shared link, When the identity provider callback is received with a valid authorization code, Then the system validates state/nonce, exchanges the code using PKCE, and extracts the verified primary email. - Then the recipient is assigned the trust tier mapped to the verified identity per workspace policy, without requiring a Timeglue account, and the session is persisted. - And if SSO is canceled or fails validation, Then the recipient remains at the previous (lower) tier. - And the assigned tier is enforced consistently across web, embeds, and calendar (ICS/API). - And only provider-verified emails elevate trust tier; unverified emails do not.
Allowlist Token Matching Assigns Allowlisted Trust Tier
- Given a shared link contains a signed allowlist token bound to a specific recipient identifier (email) and link ID, When the recipient opens the link, Then the token is validated and the recipient is assigned the allowlisted trust tier if the allowlist entry is active. - And the token includes iat/exp with expiration <= 7 days (default 48 hours), an audience (workspace/link), and a jti; tokens marked single-use are rejected on replay based on jti cache (>= 24 hours retention). - And if the allowlist entry is revoked or expired, Then the recipient is not elevated and remains at or below anonymous. - And the assigned tier is enforced consistently across web, embeds, and calendar (ICS/API). - And the smart link generator attaches allowlist tokens according to workspace invite flow settings and token TTL policy.
Signed Link Tokenization with HMAC, Rotation, and Anti-Replay
- Given any signed link or allowlist token, When validation runs, Then an HMAC-SHA256 (or stronger) signature with a rotating key (kid) is verified; tokens failing signature, audience, or expiry checks are rejected and treated as anonymous. - Then key rotation supports activating a new key immediately while honoring the previous key for a configurable grace window (default 72 hours); tokens signed with non-active keys outside grace are rejected. - And anti-replay is enforced using jti/nonce with a replay cache; replays from different IP/user agents within the token validity window are rejected unless the token is flagged multi-use in policy. - And all rejections are audit-logged with reason codes without leaking token contents. - And the smart link generator issues tokens that include kid, iat, exp, aud, jti and conform to the current workspace policy.
Short-Lived Session Persistence with Re-evaluation Per Visit
- Given a recipient has an active session, When they revisit the link, Then identity and trust tier are re-evaluated on each visit using the stored session plus any presented tokens/SSO state. - Then idle timeout <= 30 minutes and absolute session lifetime <= 24 hours are enforced; renewal beyond absolute lifetime requires re-verification or fresh token. - And if workspace policy, allowlist, or domain mapping changes, Then the recipient’s effective trust tier updates on next request within 5 minutes. - And session cookies are HttpOnly, Secure, SameSite=Lax; session identifiers are rotated on elevation to a higher tier. - And a "reset identity" action is available on the link to clear session and re-run detection without requiring a Timeglue account.
Domain-Based Mapping Assigns Partner Trust Tier
- Given the workspace configures a partner domain mapping (e.g., acme.com -> Partner tier), When a recipient completes SSO and their verified email domain matches the mapping (including supported subdomains/wildcards), Then the mapped trust tier is assigned. - And precedence rules apply: explicit allowlist > domain mapping > anonymous; denylist overrides all elevations. - And updates to domain mappings take effect for new sessions immediately and for existing sessions on next re-evaluation within 5 minutes. - And mismatched or unverified domains do not elevate tier.
Verification State Change Events via API/Webhooks
- Given a recipient’s verification state changes (e.g., anonymous -> verified SSO, verified -> allowlisted, elevated -> revoked), When the change is committed, Then an event is emitted via webhook and is available via the verification-events API. - Then webhook deliveries are signed with HMAC using a rotate-able secret, include an idempotency key, and are retried with exponential backoff for up to 24 hours; duplicates are safely ignored by consumers. - And the event payload includes: event type, link ID, workspace ID, previous tier, new tier, reason (SSO, allowlist, domain mapping, policy change), timestamp, and a stable anonymized viewer handle (hash), with no raw tokens or secrets. - And API endpoints require workspace API tokens, support filtering by link ID and time range, and return results in <= 200 ms p95 for the last 7 days of data.
Visibility Rules Engine & Data Minimization
"As a workspace admin, I want visibility rules tied to trust tiers so that sensitive availability is shared on a least-privilege basis."
Description

Create a policy engine that maps trust tiers to data exposure levels: Anonymous shows day-level open/closed; Verified (SSO/OAuth) shows hour bands and rough availability; Allowlisted shows narrow, schedulable windows that honor work hours, holidays, and focus blocks. Enforce server-side redaction of event titles, attendees, locations, and exact times outside permitted windows. Apply rules consistently across the web UI, shared embeds, ICS feeds, API responses, and notifications. Support workspace defaults and per-link overrides, with conflict resolution and safe fallbacks. Outcome: consistent least-necessary detail is shared, eliminating accidental leakage while still enabling scheduling progress.

Acceptance Criteria
Anonymous View: Day-Level Only, Full Redaction
- Given an anonymous viewer accesses a shared scheduling link, When the availability renders, Then only day-level open/closed indicators are displayed and no hour-level bands or exact times are shown. - Given an anonymous viewer requests the web UI, embed, API, or ICS feed for the link, When the server responds, Then event titles, attendees, locations, and precise timestamps are omitted or replaced with standardized placeholders. - Given an anonymous viewer appends parameters requesting higher detail (e.g., detail=full, granularity=15m), When the request is processed, Then the response still adheres to Anonymous tier day-level open/closed with full redaction. - Given dates outside the link’s shared range are requested, When processed, Then no availability or metadata is returned for those dates. - Given a target timezone is specified via the link, When the day-level view is computed, Then open/closed reflects workspace work days in that timezone.
Verified (SSO/OAuth) View: Hour Bands and Rough Availability
- Given a user authenticates via configured SSO/OAuth and is recognized as a verified partner, When accessing the link, Then hour-level bands with rough availability (Busy/Free) are displayed without exact start/end times. - Given a verified viewer calls the API or ICS feed, When the response is generated, Then availability is rounded to the configured coarse granularity (>=60 minutes) and all event metadata (titles, attendees, locations) remains redacted. - Given work hours, holidays, and focus blocks are defined, When computing hour bands, Then bands exclude non-working periods and intersect only permitted windows. - Given a verified viewer queries narrow time slices to infer details, When multiple requests are compared, Then the granularity and redaction remain consistent and do not leak finer timing. - Given the viewer’s token expires or SSO session is invalid, When the link is reloaded, Then the effective tier downgrades to Anonymous.
Allowlisted View: Narrow Schedulable Windows Honor Constraints
- Given a viewer’s email is present on the link’s allowlist, When they access the link, Then narrow schedulable windows (15–30 min granularity) are shown only within work hours, holidays, and focus blocks constraints. - Given the allowlisted viewer attempts to view outside permitted windows, When the server responds, Then exact times, titles, attendees, and locations for outside windows are redacted and replaced with placeholders. - Given the viewer attempts to book a time, When the time falls within a schedulable window, Then the booking succeeds; When it falls outside, Then the booking is rejected with a generic error that reveals no additional calendar data. - Given the allowlisted viewer accesses API/ICS, When results are returned, Then only schedulable windows and minimal fields needed to schedule are exposed; internal event metadata remains redacted.
Server-Side Redaction and Enforcement Regardless of Client
- Given requests originate from any client (web UI, embed, API, ICS) with arbitrary headers or parameters, When processed, Then all redaction and visibility decisions are applied server-side and are not bypassable by client rendering. - Given an event includes title, attendees, location, and exact start/end, When the effective policy disallows exposure, Then those fields are omitted or replaced with “[redacted]” and times are removed or rounded per tier. - Given error, timeout, or partial response scenarios occur, When the server returns errors/logs, Then no unredacted sensitive fields are present in payloads, logs, or error messages. - Given CDN or server caching is enabled, When responses are cached, Then cache keys include link ID and effective trust tier (and identity where applicable) so lower-tier requests never receive higher-tier content.
Workspace Defaults, Per-Link Overrides, and Conflict Resolution
- Given workspace-level defaults exist and a link has no overrides, When the link is accessed, Then defaults determine the exposure for each tier. - Given a per-link override is stricter than the workspace default, When conflicts are resolved, Then the stricter (least-detail) rule applies. - Given a per-link override is more permissive than the workspace default, When conflicts are resolved, Then the most restrictive effective policy is applied. - Given identity is ambiguous or missing, When the link is accessed, Then the safe fallback is Anonymous tier. - Given workspace defaults are updated, When an existing link is loaded thereafter, Then new defaults apply unless an explicit per-link override is present.
Cross-Channel Consistency: UI, Embeds, ICS, API, Notifications
- Given the same link is accessed via web UI, embed, API, ICS, and notifications for the same identity, When responses are compared, Then availability granularity and redaction are identical across channels. - Given a notification (email/Slack/push) is triggered by a view or booking, When the template is populated, Then only fields permitted by the effective trust tier are included. - Given a user’s trust tier is downgraded (removed from allowlist or SSO access revoked), When subsequent requests and notifications occur, Then reduced visibility takes effect immediately on the next request and no later than 60 seconds. - Given an embed is hosted on a third-party site and loaded without authentication, When it renders, Then it adheres to Anonymous tier with no leakage.
Allowlist & Partner Directory Management
"As a team lead, I want to allowlist partners by email or domain so that trusted contacts can access narrower scheduling windows."
Description

Provide UI and API to manage allowlisted entities by email, domain, and partner organization, assigning each a default trust tier and optional expiry. Support bulk import (CSV), domain wildcards, per-link overrides, and justification notes. Include verification flows for first-time viewers, soft matches (same domain) vs. hard matches (exact email), and conflict resolution when multiple policies apply. Integrate with workspace roles, existing contacts, and external systems via webhook/Zapier to sync partner directories. Outcome: trusted contacts seamlessly gain access to narrower windows without exposing full availability to unknown recipients.

Acceptance Criteria
Create and Manage Allowlist Entries with Tiers and Expiry (UI & API)
Given an Admin user is on Allowlist management or authenticated to the API When they create an entry of type "email", "domain", or "organization" with a valid value, select a trust tier, and set an optional expiry >= now Then the entry is saved with a unique ID, selected trust tier, expiry (or none), creator, and timestamp, and is visible in list and retrievable via API Given an email value When value is validated Then it must conform to RFC 5322 local@domain and not contain wildcards Given a domain value When value is validated Then it must be a valid registrable domain and may optionally start with "*." to indicate wildcard; "*.example.com" matches all subdomains of example.com but not the root "example.com" Given an organization value When value is validated Then it must match an existing partner organization record or be created atomically with the allowlist entry Given a duplicate value and type already exists and is active When creation is attempted Then the system rejects with a descriptive error and no duplicate is created Given a non-Admin user attempts to create/update/delete entries When the action is submitted Then the system denies with 403 and no changes occur Given an entry reaches its expiry timestamp When a viewer matches only via that entry Then the entry no longer grants elevated trust and the viewer falls back to the next applicable policy Given an entry is deleted or updated When the action is confirmed Then the change is recorded in an audit log with actor, before/after values, reason (if provided), and timestamp
Bulk CSV Import with Validation, Preview, and Partial Apply
Given an Admin uploads a CSV with header "type,value,trust_tier,expiry,justification" When they request a dry-run preview Then the system validates up to 10,000 rows and 5 MB, returning counts of create/update/reject and row-level errors with line numbers and reasons without persisting changes Given a valid preview has been generated within the last 15 minutes When the Admin confirms import Then only rows that passed validation are applied, rows that failed are skipped, and a summary with IDs and counts is returned Given duplicate rows within the same file When import runs Then the last occurrence wins per "type,value" and prior duplicates are marked as superseded in the preview Given an expiry column is provided When parsing Then values must be ISO 8601 with timezone or date-only (interpreted as 23:59:59 UTC), otherwise the row is rejected Given trust_tier values are provided When parsing Then they must match one of the configured trust tier keys; otherwise the row is rejected Given import completes When webhooks are enabled Then a single "allowlist.bulk_import.completed" event is emitted with summary and correlation ID
First-Time Viewer Verification and Tiered Visibility
Given an anonymous viewer opens a smart link When no verification has been performed Then the viewer sees only day-level open/closed availability Given a viewer authenticates via supported SSO/OAuth provider and email is verified When no allowlist policy applies Then the viewer is assigned the "verified partner" tier and sees hour-band visibility Given a viewer authenticates and matches an allowlist entry (email/domain/organization) that grants a higher tier When access is evaluated Then the higher tier is applied and the viewer sees narrower windows accordingly Given a viewer’s previous verification cookie is older than 7 days or they sign out When they access a link Then they are treated as anonymous until they re-verify Given an expired allowlist entry When a previously matched viewer returns after expiry Then the viewer’s applied tier is re-evaluated and reduced per remaining policies
Soft vs Hard Match and Conflict Resolution Rules
Given multiple policies could apply to a viewer When determining the effective trust tier Then precedence is applied in this order: per-link override > exact email (hard match) > partner organization > domain (including wildcard) > verified partner default > anonymous Given two policies at the same precedence level grant different tiers When resolving Then the more restrictive tier is applied Given a wildcard domain entry "*.example.com" When matching Then it matches any subdomain of example.com (e.g., a.b.example.com) but not example.com itself Given an exact domain entry "example.com" When matching Then it matches the root domain only and not subdomains Given a viewer matches both an email entry and a domain entry with different tiers When accessing a link Then the email entry’s tier is applied Given a per-link override is configured to reduce visibility relative to a viewer’s policy-derived tier When accessing that link Then the override’s reduced tier is applied
Per-Link Trust Tier Overrides
Given a link owner with Manage permissions opens link settings When they set a trust tier override for the link Then the override is saved and applied only to that link without changing global policies Given an override attempts to elevate a viewer’s visibility above their maximum policy-derived tier When saving Then the system rejects the change with an error and no override is stored Given an override reduces visibility relative to a viewer’s policy-derived tier When the viewer accesses the link Then the reduced visibility is enforced for that link Given an override is edited or cleared When changes are saved Then the effective tier for future visits reflects the latest override and an audit log entry is recorded
Justification Notes Capture and Exposure
Given a user creates or updates an allowlist entry When they provide a justification note up to 500 characters Then the note is stored with the entry and included in audit logs and exports Given no justification note is provided When the entry is created or updated Then the operation still succeeds and the note is stored as null Given an existing entry with a justification note When the note is changed Then the previous note is retained in history with timestamp and actor, and the new note becomes the current value
Partner Directory Sync via Webhook/Zapier
Given a configured Zap or webhook endpoint sends a partner directory upsert with idempotency key, type (email/domain/organization), value, trust_tier, and optional expiry When the payload is received Then the system upserts the corresponding allowlist entry atomically and returns 2xx on success Given the same idempotency key is retried within 24 hours When processed Then no duplicate change is applied and a 2xx is returned Given the receiver endpoint fails transiently When emitting outgoing events Then the system retries at least 3 times with exponential backoff and dead-letters after final failure Given a payload marks an entry as "removed" When processed Then the corresponding allowlist entry is deleted or deactivated, and downstream "allowlist.entry.removed" event is emitted Given workspace role constraints When sync runs Then external automations can perform changes regardless of end-user role, but all changes are attributed to the integration and audited
Smart Link Permissions & Expiry Controls
"As a link owner, I want to set expiry and revocation on shared links so that access to availability details can be time-bound and controlled."
Description

Extend Timeglue smart links to bind a specific trust-tier policy, set expiration timestamps, enable one-time access, and allow instant revocation. Provide optional PIN protection and rate limiting to mitigate unwanted sharing and scraping. Surface link health (active/expired/revoked), last accessed timestamp, and top viewer tiers in the link dashboard. Ensure backward compatibility for existing links, and provide migration prompts to attach a policy. Outcome: link owners control how long and how widely detailed availability is visible, reducing risk while maintaining frictionless sharing.

Acceptance Criteria
Bind Trust-Tier Policy to New Smart Link
Given a user is creating a new smart link When the user selects a trust-tier policy and saves the link Then the link enforces the policy as follows: And Anonymous viewers see day-level open/closed only (no specific hours) And Verified viewers via SSO/OAuth see hour bands And Allowlisted contacts see minute-level narrow windows configured by the owner And the selected policy identifier is persisted and shown in link details And attempts to access a restricted tier without required identity trigger an authentication challenge And access logs record the viewer tier for each visit
Expiration and Revocation State Enforcement
Given a smart link has an expiration timestamp set in UTC When the current time is before the expiration Then the link remains accessible per its trust-tier policy Given the current time is on or after the expiration When any viewer accesses the link Then the viewer sees an Expired state and no availability details are returned And the link health displays Expired in the dashboard within 1 minute Given the owner clicks Revoke Link in the dashboard and confirms When revocation completes Then all subsequent requests are blocked with a Revoked state within 60 seconds globally And previously cached tokens or previews are invalidated
One-Time Access Control
Given a smart link is configured for one-time access When the first qualifying human viewer successfully accesses the link Then the trust-tier policy is applied for that visit and the link is immediately marked Consumed And any subsequent access attempts return Expired/Consumed without revealing availability And bot/preview user agents and HEAD requests do not consume the link And the dashboard records the consumer identity (tier and identifier when available) and timestamp
PIN Protection Validation
Given a smart link has PIN protection enabled with a 6-digit numeric PIN When any viewer attempts to access the link Then the viewer is prompted for the PIN before any availability is revealed And entering the correct PIN grants access per the trust-tier policy And 5 consecutive failed PIN attempts from the same IP or authenticated account result in a 15-minute lockout with HTTP 429 and a Retry-After header And the PIN is never included in URLs or logs and the input is masked client-side
Anti-Scraping Rate Limiting
Given rate limiting is enabled for smart links When requests exceed 30 per minute or 200 per hour per IP per link Then responses return HTTP 429 with a Retry-After header and no availability payload And requests within limits succeed normally And the dashboard surfaces a Rate limited event count per link
Dashboard Health, Last Accessed, and Top Viewer Tiers
Given a smart link is receiving viewer traffic When the owner opens the link dashboard Then the Link Health status is shown as one of Active, Expired, or Revoked And the Last accessed timestamp reflects the most recent successful access within 2 minutes of occurrence And Top viewer tiers displays counts and percentages by tier (Anonymous, Verified, Allowlisted) for the last 30 days, sorted by volume
Backward Compatibility and Migration Prompt for Existing Links
Given a smart link created before Trust Tiers with no policy attached When any viewer accesses the link Then the link behaves as before (no trust-tier gating, no PIN, no expiration) until a policy is attached And the dashboard labels the link Legacy — No Policy and displays a migration prompt When the owner completes the migration flow and selects a policy Then the existing URL continues to work without change and the selected policy is enforced on subsequent accesses And no downtime exceeds 5 seconds during migration and historical analytics are retained
Tier Preview & Recipient Upgrade UX
"As a recipient, I want guidance to verify my identity to see more detail so that I can schedule without back-and-forth."
Description

Add creator-side previews for each trust tier to validate what recipients will see before sharing, with instant toggles and redaction highlights. For recipients, display a clear, non-intrusive banner indicating current tier and a call to verify via SSO to unlock more detail when permitted. Support inline verification without losing context, localized copy, accessible components, and mobile-responsive layouts. Provide edge-case flows for expired links, denied verification, and unsupported IdPs. Outcome: users confidently share least-necessary detail and recipients can voluntarily upgrade their tier to schedule faster.

Acceptance Criteria
Creator Preview: Tier Toggles & Redaction Highlights
Given I am the link creator on the Share screen with a schedule configured When I toggle between Anonymous, Verified Partner, and Allowlisted tiers Then the preview updates within 300ms to reflect the selected tier’s visibility rules Given a time segment is hidden at the selected tier When the preview renders Then the segment is replaced by a clearly marked redaction with text label "Redacted" and aria-label "Redacted time" with contrast ratio ≥ 4.5:1 Given server-side rendering for the selected tier When I open the Recipient View from preview Then the recipient view matches the creator preview for that tier in content and layout (visual parity within 2px tolerance) Given a share link is generated When a recipient with the tier matching the preview opens it Then the level of detail exactly matches what the preview displayed for that tier
Recipient Tier Banner & Upgrade CTA
Given a recipient opens a share link as Anonymous and upgrades are permitted by the link When the page loads Then a non-intrusive banner appears indicating current tier and a "Verify to see more" CTA; banner height ≤ 72px on mobile and does not overlay timeline content Given a recipient is already verified to the highest allowed tier When the page loads Then the banner reflects the current tier and hides the upgrade CTA Given the banner is dismissed by the recipient When the page is reloaded in the same session Then the banner remains dismissed unless the tier changes Given the banner is rendered When navigated with keyboard or screen reader Then the banner is focusable, the CTA is reachable, and the banner is announced via a polite live region with no focus theft
Inline Verification Without Losing Context
Given a recipient clicks "Verify to see more" When the SSO/OAuth flow starts Then the verification opens inline (modal or in-page) and scroll position and selected date are preserved Given the IdP returns success and the link permits an upgrade When the callback is received Then the recipient’s tier upgrades within 2 seconds and the view updates in place without full page reload Given the IdP returns cancel or error When the callback is received Then the recipient remains at the prior tier and a non-blocking error message with a retry option is shown Given focus was on the banner CTA before verification When returning from the IdP Then focus is restored to the prior element or moved to the upgraded content region with an aria-live polite announcement
Localization & Internationalization
Given the recipient’s locale is supported (en-US, fr-FR, de-DE, es-ES, ja-JP) When the banner, CTA, errors, and redaction labels render Then all text is localized and dates/times use locale-appropriate formats (12/24h as per locale) Given a translation key is missing When UI renders Then English (en-US) fallback strings are displayed without placeholder tokens Given localized strings are up to 30% longer than English When rendered on mobile and desktop Then no text is clipped or overlaps and primary actions remain visible Given a right-to-left locale is active When viewing the page Then layout and icons mirror appropriately and reading order is correct
Accessibility Compliance (WCAG 2.2 AA)
Given any interactive element in the preview or banner When navigating via keyboard Then all elements are reachable in logical order with visible focus indicators (contrast ≥ 3:1) and no keyboard traps Given content changes due to tier switch or verification When the view updates Then changes are announced via aria-live="polite" and focus is not hijacked unexpectedly Given text and icons in banners, buttons, and redactions When measured for contrast Then normal text meets ≥ 4.5:1 and large text/icons meet ≥ 3:1 contrast ratios Given a screen reader is used When exploring redacted areas and the tier banner Then redactions expose descriptive labels and the banner uses appropriate landmarks/roles for clear context
Mobile Responsiveness
Given a device width between 320px and 768px When viewing creator previews and recipient pages Then layout adapts without horizontal scrolling, tap targets are ≥ 44x44px, and safe area insets are respected Given the tier banner is displayed on mobile When the user scrolls Then the banner does not obscure timeline interactions and can be dismissed or collapsed Given the creator switches tiers on mobile When the toggle is used Then the control is reachable with one hand and the preview reflows within 300ms without content jump exceeding 24px
Edge Cases — Expired Links, Denied Verification, Unsupported IdPs
Given a recipient opens an expired or revoked link When the page loads Then a dedicated "Link expired" state is shown, no schedule details are revealed, and a "Request new link" action is available if enabled Given a recipient attempts verification but is not allowlisted or the IdP denies access When the flow completes Then the tier remains unchanged, a clear "Access not granted" message is shown, and no additional details are displayed Given the recipient selects an unsupported IdP When attempting to verify Then the UI explains the unsupported provider and offers supported options or a fallback to remain at current tier Given network failures occur during verification When the user retries Then the UI allows up to 3 retries with exponential backoff and no partial upgrades occur Given analytics capture is enabled When the banner is shown, the CTA is clicked, and verification succeeds or fails Then events are logged without PII and are associated to the link ID for troubleshooting
Audit Logging & Access Reports
"As a compliance owner, I want an audit trail of access by trust tier so that I can demonstrate and review appropriate data exposure."
Description

Capture immutable, privacy-aware logs of access events including link ID, viewer identity signals (email/domain/IdP), assigned trust tier, timestamps, geo/IP (masked), and viewed scope (day-level/hour bands/narrow windows). Provide workspace-level reports and exports with filters (time range, link, tier, partner), retention policies, and SIEM/syslog export. Trigger alerts for unusual patterns (e.g., excessive anonymous hits, cross-region scraping). Integrate with Timeglue’s admin console and existing analytics. Outcome: organizations can demonstrate compliance, investigate incidents, and optimize trust policies.

Acceptance Criteria
Log Fields and Masking for Trust-Tier Access Events
Given any access to a shared Timeglue link by a viewer at any trust tier When the access event is recorded Then the log entry includes: event_id (UUIDv4), timestamp_utc (ISO 8601 ms), workspace_id, link_id, assigned_trust_tier (anonymous|verified_partner|allowlisted), viewed_scope (day_level|hour_bands|narrow_windows), viewer_identity: email (encrypted at rest), domain, idp.provider and idp.assertion_id (when applicable), geo_country (ISO 3166-1 alpha-2), ip_cidr (IPv4 /24 or IPv6 /48), user_agent_hash (SHA-256), action (view|export|attempt), outcome (allowed|blocked|rate_limited) And raw IP addresses are not persisted; only ip_cidr and geo_country are stored And email values are encrypted at rest; a salted hash is stored for aggregation and joins And viewed_scope reflects the maximum detail actually shown to the viewer per trust tier
Tamper-Evident, Append-Only Audit Store
Given any attempt to modify or delete an existing audit log record outside retention processing When the request is made via UI, API, or backend process Then the system rejects the mutation and records a security event; API returns 403 Forbidden And logs are stored append-only with a per-record record_hash and prev_hash to form a verifiable hash chain And a daily integrity job verifies chain continuity and emits an integrity report; any failure triggers a critical alert
Workspace Reports, Filters, and Export
Given a workspace admin opens Audit Logs and applies filters by time range (UTC), link_id, assigned_trust_tier, partner domain, outcome, and action When the query is executed on a dataset up to 1,000,000 records Then the filtered results return within 3 seconds for the 95th percentile and are paginated And the result schema shows all required fields with masked PII by default And CSV and JSON exports reproduce the filtered result; email is only unmasked when the requester has Audit PII permission And exports include a header with field names and generation metadata (workspace_id, generated_at_utc, filter_summary)
Real-Time SIEM/Syslog Streaming
Given a workspace admin configures a SIEM/syslog destination with endpoint, protocol (RFC5424 TCP/TLS or HTTPS), and credentials When new audit events are produced Then events are streamed to the destination within 60 seconds of ingestion, preserving order per link_id via sequence_number And delivery is at-least-once with retries and exponential backoff; failures are queued durably for 7 days And the exported event schema matches the internal log schema with PII masking consistent with workspace settings
Anomaly Alerts: Excessive Anonymous Hits & Cross-Region Scraping
Given anomaly thresholds are configured (defaults: anonymous_hits_threshold=50 in 10 minutes per link per /24; cross_region_threshold=5 distinct countries in 60 minutes per link) When events meet or exceed a threshold Then an alert is generated within 2 minutes and sent to workspace admins via email and Slack webhook with link_id, counts, time window, sample ip_cidr/geo_country, and recommended actions And alerts are deduplicated to at most one per pattern per link per 15 minutes; all alerts are written to the audit log
Configurable Retention and Legal Hold
Given a workspace retention policy is set (e.g., 90/180/365 days) and optional legal holds by link_id or partner domain When records exceed the retention period and are not under legal hold Then a nightly purge job deletes the eligible records and writes a purge summary record including job_id, time_range, and deleted_count And legal hold prevents deletion while active and is itself audited (created_by, reason, start/end timestamps) And admins can schedule an export of records due to purge and receive it before deletion completes
Admin Console and Analytics Integration
Given an admin views a specific link or partner in the Admin Console When they open the Access Reports tab Then the table displays the corresponding audit entries filtered to that context and a summary breakdown by trust tier and viewed_scope And only users with Admin or Security Analyst roles can access this view; others see a 403 error And counts in the summary match existing analytics within 1% over the same date range

Jitter Guard

Protect against inference and scraping with randomized boundary jitter, response caching, and anomaly detection. Suspicious patterns are throttled or challenged, keeping masked windows useful to humans but opaque to automated collectors.

Requirements

Configurable Boundary Jitter Engine
"As a meeting organizer, I want my shared availability to be slightly obfuscated without changing what can actually be booked so that bots can’t infer exact boundaries but humans can still pick a time quickly."
Description

Introduce controlled randomization to the displayed start/end boundaries of availability windows on smart links and embeds, making precise inference by automated collectors impractical while preserving human usability. Jitter amplitudes (e.g., ±3–7 minutes) are tenant-configurable with safe caps and never permit bookings outside true constraints (work hours, holidays, focus blocks). The UI renders jittered boundaries, but server-side booking enforcement uses the canonical windows, ensuring no after-hours invites. Jitter is seeded per link and viewer context (e.g., link ID + coarse UA/geo + day) using a secret to prevent averaging attacks across repeated requests while keeping a consistent view within a short session. Integrates with link rendering API, calendar previews, and ICS exports (exports remain canonical). Includes ramp controls, A/B flags, and audit logs.

Acceptance Criteria
Tenant Jitter Amplitude Configuration & Safe Caps
Given a tenant admin with manage-permissions When they save jitter amplitude min=3 and max=7 minutes via UI or API Then the values are validated (0 ≤ min ≤ max ≤ 10) and persisted And a subsequent GET /tenant/{id}/jitter-config returns min=3,max=7 Given a tenant admin When they attempt to save min or max outside safe caps (e.g., min=-1 or max=12) Then the request is rejected with HTTP 400 and field-level error messages And no configuration change is persisted Given a tenant with no explicit jitter configuration When the system is queried for jitter settings Then the default amplitude range is applied as 3–7 minutes (within safe caps) And the default is marked as source=default in the response
UI Jitter Rendering Within Canonical Boundaries; Booking Enforcement Canonical
Given a canonical availability window 09:00–17:00 local time and configured jitter 3–7 minutes When the link or embed renders Then the displayed start is clamped to [09:03, 09:07] and the displayed end to [16:53, 16:57] And neither displayed boundary falls outside the canonical window Given a very short canonical window (duration < 2 × min jitter) When rendering with jitter configured Then jitter offsets are reduced as needed so that start < end and both remain within the canonical window And a minimum visible duration of at least 1 minute is maintained Given any UI-rendered jittered window When a viewer attempts to book a time outside the canonical window (even if within the jittered display) Then the booking service validates against canonical windows and returns HTTP 409 with reason=OutsideCanonical Given a booking request inside the canonical window but outside the jittered display When submitted to the booking service Then the booking is accepted if no other constraints are violated
Deterministic Per-Viewer Seeding With Session Consistency
Given inputs: linkId, date(UTC), uaBucket, geoBucket, featureVersion and a server-held secret When computing jitter Then the seed is HMAC-SHA256(secret, linkId|dateUTC|uaBucket|geoBucket|featureVersion) And the resulting start/end offsets are derived from the seed within the configured amplitude range (integer minutes) Given the same viewer context (same linkId, uaBucket, geoBucket) on the same UTC date When the link is fetched repeatedly within 15 minutes Then the displayed jittered boundaries remain identical across requests Given the same viewer context across a UTC date change or a change to uaBucket or geoBucket When the link is fetched after the change Then a different seed is produced and the displayed jitter may change accordingly (still within configured bounds) Given a secret rotation event When new requests arrive Then seeds and displayed jitter change immediately And any caches tied to the prior secret are bypassed or invalidated
Cache Keying & TTL for Jittered Responses
Given server-side response caching is enabled for link rendering When serving a jittered availability response Then the cache key includes linkId, dateUTC, uaBucket, geoBucket, rampVariant, and featureVersion And HTTP Vary headers include UA-bucket and Geo-bucket indicators Given a cached jittered response When the same key is requested within the TTL Then the identical jittered boundaries are returned from cache Given the caching policy When determining TTL Then TTL is set to the lesser of 15 minutes or time-until next UTC day Given a change to jitter configuration, seed secret, or ramp settings When subsequent requests are processed Then the relevant cache entries are invalidated or bypassed ensuring updated behavior
Integrations: Link API/Embeds Jittered; ICS Exports Canonical
Given the public link rendering API or embeddable widget When availability is fetched Then the UI/JSON includes jittered start/end boundaries per configured rules Given an ICS export/download for the same availability When the .ics file is generated Then all event start/end times use canonical (non-jittered) times with correct timezone And no jitter metadata is present in the ICS Given an in-page calendar preview component When rendering availability Then it reflects the jittered boundaries shown by the link/embedding API for consistency
Ramp Controls & A/B Flags Behavior
Given ramp=0% When any viewer loads an availability link Then jitter is disabled and canonical boundaries are rendered Given ramp=50% When viewers load the link Then each viewer is deterministically assigned using the same seeding context to either control (no jitter) or variant (jitter) And the assignment remains stable for that viewer for the UTC date Given ramp=100% When any viewer loads the link Then jitter is applied for all viewers (subject to feature flag overrides) Given a feature flag override forcing OFF for a tenant or link When ramp would otherwise include a viewer Then jitter remains disabled
Audit Logging for Jitter Configuration & Activation
Given a tenant admin changes jitter configuration or ramp/flag settings When the change is saved Then an audit log entry is written with fields: tenantId, actorId, action, oldValue, newValue, timestamp, sourceIpHash And secrets (e.g., HMAC secret) are never logged Given audit logs are queried via GET /tenant/{id}/audit?category=jitter When the request is made by an authorized admin Then the entries are returned in chronological order within 2 seconds of the action Given a secret rotation event for jitter seeding When it occurs Then an audit entry is recorded indicating rotation occurred without exposing the secret value
Cache-Bucketed Response Layer
"As a link viewer, I want fast and consistent availability responses so that I can scan for a time without delays while preventing scrapers from learning more by making tiny request changes."
Description

Add a response caching tier for smart link availability that groups requests into coarse buckets (e.g., 15-minute time slices, device class, region) so repeated probes receive identical cached responses, reducing variance signals used by scrapers. Configure sensible TTLs and cache keys to avoid leaking fine-grained differences (query param sweeps, minute-by-minute changes). Ensure correct Vary headers, CDN compatibility, and cache stampede protection. Provide cache bypass for authenticated owners and canonical booking flows while maintaining opaque, stable outputs for anonymous viewers. Include cache invalidation on schedule changes and guard against timing-based inference by padding generation times as needed.

Acceptance Criteria
Anonymous Viewer Receives Bucketed Cached Availability
Given an anonymous user requests the same smart link within a 15-minute time bucket from the same device class and region, When the first request is processed, Then subsequent requests within that bucket return cache hits with byte-identical response bodies and stable availability slots. Given two requests in the same bucket differ only by query parameter order or include unknown query parameters, When responses are compared, Then the payload and ETag are identical and CDN reports a cache hit. Given requests straddle adjacent 15-minute buckets, When responses are compared, Then payload differences appear only at bucket boundaries and not minute-by-minute. Given 10 repeated requests within the same bucket, When headers are inspected, Then Cache-Control, ETag, Age (increasing), and Surrogate-Control are consistent with a cached response and no per-request variance is exposed in the body.
Bypass for Authenticated Owners and Canonical Booking Flow
Given a logged-in owner with a valid session requests availability, When the response is generated, Then the public cache is bypassed (e.g., Cache-Control: private, no-store; X-Cache: BYPASS) and the response reflects the latest persisted state. Given a canonical booking flow request (identified by a valid flow token) fetches availability or places a hold, When the response is generated, Then it bypasses the shared cache and is not stored in or pollute public cache keys. Given an authenticated or booking-flow request is followed by an anonymous request for the same key, When the anonymous request is served, Then it is fulfilled from the public cache if valid and contains no private data.
Cache Invalidation on Schedule Update
Given a user edits work hours, focus blocks, or holidays and saves, When the write is acknowledged, Then all affected cache keys are purged or version-bumped within 5 seconds. Given cached responses overlap the changed intervals, When the next anonymous request arrives post-invalidation, Then the response reflects the change and carries a new ETag distinct from the previous value. Given unaffected intervals and buckets, When requested after the change, Then their cache entries remain valid and are not purged. Given a save occurs during an in-flight revalidation, When revalidation completes, Then stale pre-change content is not reinserted into the cache.
Stable Responses Under Query Parameter Sweeps
Given an actor sweeps minute-level parameters or appends unknown query parameters to a smart link URL, When requests map to the same time/device/region bucket, Then the cache key ignores unknown parameters and normalizes known parameters so the same cached object is served. Given two URLs differing only by query parameter order or encoding variants, When requested, Then they produce identical response bodies and ETags and receive cache hits from the same canonical key. Given a supported parameter changes the bucket (e.g., date), When the parameter crosses buckets, Then responses change only at bucket boundaries and never minute-by-minute. Given 100 requests with randomized non-whitelisted parameters, When observed, Then at least 99% of responses are cache hits mapped to the canonical key.
CDN Compatibility with Correct Vary Headers
Given an anonymous cacheable response is returned, When headers are inspected, Then Cache-Control includes public, max-age >= 300, stale-while-revalidate >= 300, and Surrogate-Control is set for edge caching. Given responses differ by device class and region buckets, When headers are inspected, Then Vary includes X-Device-Class and X-Region (or CDN geo header) and Accept-Encoding, and does not include volatile headers (e.g., Date, Authorization). Given the same request is repeated through the CDN, When observed, Then the CDN serves from edge after the first origin fill and the Age header monotonically increases. Given query parameter order variations pass through the CDN, When routed to origin, Then URL normalization or origin cache key normalization ensures the same cached object is used.
Cache Stampede Protection on Cache Misses
Given 100 concurrent anonymous requests for a cold cache key within 1 second, When processed, Then at most one origin recomputation occurs and others coalesce or receive stale-while-revalidate content; origin QPS for that key <= 1. Given cached items approach expiry, When TTL jitter is applied, Then refreshes are distributed so no more than 20% of keys refresh within the same 5-second window under steady load. Given a backend latency spike or transient error, When serving cacheable endpoints, Then stale-if-error up to 60 seconds is honored and no more than 1% of requests receive 5xx.
Latency Padding to Prevent Timing-Based Inference
Given a mix of cache hits and misses for anonymous requests under nominal load, When TTFB is measured for 200 samples each, Then the difference between hit and miss medians and p95s is <= 50 ms due to deterministic padding. Given a response is a cache hit for an anonymous viewer, When sent, Then an artificial delay of 10–40 ms jitter is applied and never exceeds the configured maximum padding. Given padding is applied, When headers are inspected, Then X-Timing-Pad: true (or equivalent) is present for anonymous cached responses and absent for private/bypassed responses.
Adaptive Anomaly Scoring & Throttling
"As a workspace admin, I want automated detection of scraping patterns with proportional throttling so that the system protects our availability data without disrupting legitimate scheduling."
Description

Implement a real-time scoring pipeline that detects suspicious fetching patterns such as high request rates, parameter sweeps, coordinated IP rotations, inconsistent headers, and off-hours probing. Use sliding windows and weighted signals to compute a risk score that triggers graduated responses: soft rate limits, response delay/padding, degraded data, or block/challenge. Operate initially in shadow mode to tune thresholds, then enforce with tenant-level overrides and safe defaults. Log incidents with evidence for review and support targeted allow/deny rules. Integrates at API gateway and application layers for consistent behavior across web, embed, and API endpoints.

Acceptance Criteria
Shadow Mode Risk Scoring Does Not Alter Responses
Given enforcement mode is set to Shadow for tenant T And risk scoring is enabled at both gateway and application layers When a client performs a high-risk pattern (>=120 requests in 60 seconds to protected endpoints) Then the computed risk score is recorded with signal contributions and timestamps And no response status codes, bodies, or headers differ from baseline for identical inputs (p99 latency delta <= 5 ms) And no throttling, delay, degradation, or blocks/challenges are applied And telemetry for risk score distribution and top signals is emitted within 60 seconds
Sliding Window Rate Spike Triggers Soft Rate Limit
Given tenant T is in Enforce mode with rate_spike_threshold = 100 requests per 60 seconds When a single client exceeds 100 requests within a sliding 60-second window Then requests beyond the threshold receive HTTP 429 with Retry-After: 15 And the soft limit lifts automatically when the moving window drops below the threshold for at least 10 seconds And an incident is recorded with window statistics and client identifiers
Parameter Sweep Detection Adds Response Delay and Data Degradation
Given tenant T is in Enforce mode and the parameter_sweep rule is configured as 12+ distinct query parameter permutations to the same resource within 60 seconds When a client exhibits a parameter sweep against availability endpoints Then the risk score increases by the configured parameter_sweep weight and crosses the delay/degrade action threshold And subsequent 200 responses to that client include an artificial delay of 300 ms ± 50 ms jitter And returned availability windows are degraded to the nearest 30-minute granularity And responses include X-JG-Action: delay and a correlation header X-Trace-Id
Coordinated IP Rotation Aggregation and Cross-IP Throttling
Given aggregation groups requests by auth token and by IP /24 When three or more distinct IPs within the same /24 issue a combined >=60 requests to the same tenant within 60 seconds with matching client fingerprint Then the aggregate risk score crosses the throttle action threshold And a throttle of 1 request per second per token is applied across all participating IPs for 5 minutes And attempts above the cap receive HTTP 429 with Retry-After: 1 And the incident log includes hashed IPs, token, fingerprint evidence, and window statistics
Off-Hours Probing Increases Risk and Triggers Challenge
Given tenant T has defined work hours, holidays, and focus blocks And off-hours are outside configured work hours and not holidays/focus exceptions When a client generates >=20 requests within 5 minutes during off-hours to discovery endpoints Then the risk score is increased by the configured off_hours weight And if the score >= challenge threshold, the client receives a 302 to a challenge endpoint with a one-time token valid for 5 minutes And on successful challenge, an allow token grants a 10-minute grace period; otherwise subsequent protected requests receive HTTP 403 for 10 minutes
Tenant-Level Overrides Apply Safely with Fallbacks
Given an authorized admin submits override settings for thresholds and actions via the admin API When the payload is syntactically and semantically valid Then the configuration is audited and applied within 60 seconds without service restart And requests for tenant T reflect the new thresholds and actions And invalid override payloads are rejected with HTTP 400 and defaults remain active And all changes are logged with actor, old/new values, timestamp, and a rollback entry
Incident Logging with Evidence, Redaction, and Traceability
Given any mitigative action (delay, degrade, throttle, challenge, block) is triggered When the incident is persisted to the log store Then the record includes correlation_id, tenant_id, action, total risk score, top 5 signal contributions with weights, sliding window stats, exemplar request_ids, and participating IP hashes And sensitive fields (PII, tokens, cookies, full URLs) are redacted or irreversibly hashed And incidents are queryable via console/API within 2 minutes and retained for 30 days And the correlation_id is returned to the client via X-Trace-Id on mitigated responses
Human Verification Challenge Gate
"As a legitimate user who triggers a safeguard, I want a quick, accessible verification step so that I can continue scheduling without being blocked by anti-scraping defenses."
Description

Provide an adaptive challenge flow (e.g., Turnstile/hCaptcha, proof-of-work, or simple interaction checks) that activates only when the anomaly score exceeds a threshold or when behavior continues after throttling. While challenged, show blurred or coarsened availability until completion; after pass, grant normal access for a grace window. Ensure accessibility, localization, and mobile usability; exempt authenticated owners and whitelisted service IPs. Record challenge outcomes to retrain thresholds and reduce false positives. Integrate with the link viewer, embed, and API error model with clear messages and retry guidance.

Acceptance Criteria
Challenge Activation on High Anomaly Score or Continued Throttling
Given a viewer is neither authenticated nor from a whitelisted IP and their anomaly_score >= configured_threshold, When they request availability via link viewer, embed, or API, Then the system presents a human verification challenge before returning unmasked availability, And API responses use HTTP 403 with error_code=challenge_required and retry guidance. Given a viewer has been throttled within throttle_window and continues to exceed continuation_rate, When further requests arrive, Then a human verification challenge is presented even if anomaly_score < configured_threshold. Given multiple challenge types are enabled, When a challenge is issued, Then the type is selected per configured policy (e.g., weighted rotation) and recorded in telemetry.
Masked Availability During Challenge
Given a challenge is pending, When the availability view renders, Then availability is blurred or coarsened to day-level buckets with no exact times, And selection and booking interactions are disabled. Given a challenge is pending, When the API is called for precise slots, Then the API returns HTTP 403 with error_code=challenge_required and does not include exact times, And includes retry_after or challenge_url for guidance. Given a challenge is completed successfully, When the view refreshes or the client retries, Then unmasked availability is displayed or returned in the next response.
Post-Pass Grace Window
Given a viewer passes the human verification, When subsequent requests occur within grace_window duration, Then normal access is granted without additional challenge and throttling counters are reset. Given the grace_window has expired, When the viewer requests availability, Then anomaly detection rules apply and may trigger a new challenge. Given a viewer passes on one device or browser profile, When the same link is accessed from a different device or fresh profile, Then the previous grace does not apply and challenge may be required based on anomaly rules.
Accessibility, Localization, and Mobile Usability
Given a keyboard-only user, When interacting with the challenge gate, Then all focusable elements are reachable in logical order with visible focus states and completion is possible without a pointer. Given a screen reader is active, When the challenge gate is shown, Then controls and status messages have accurate labels and roles and the challenge can be completed non-visually. Given localized UI strings are available, When the viewer’s locale is detected via Accept-Language or explicit selection, Then all challenge and error texts render in that locale with correct number/date formats. Given a mobile device with viewport width ≤ 375px, When the challenge gate renders, Then layout is responsive, tap targets are ≥ 44×44 px, and no horizontal scrolling occurs. Given the third-party challenge provider fails to load, When the gate is invoked, Then a first-party accessible fallback (e.g., simple interaction or proof-of-work) is offered with the same pass/fail semantics.
Exemptions for Authenticated Owners and Whitelisted Service IPs
Given an authenticated owner of the link/calendar, When viewing their own link via viewer, embed, or API, Then no challenge or throttling is applied regardless of anomaly score. Given a request originates from a whitelisted service IP or includes a valid service token with whitelisted scope, When accessing availability, Then no challenge is applied and normal responses are returned. Given an exempted request, When events are logged, Then the exemption_reason is recorded without issuing a challenge.
Integration and Error Model for Link Viewer, Embed, and API
Given the public link viewer, When a challenge is required, Then the page displays an inline challenge with clear message and retry guidance and preserves viewer context (participants, filters) across completion. Given the embeddable widget in an iframe, When a challenge is required, Then an overlay challenge renders within the iframe and posts challenge_state events to the parent window via postMessage without breaking embedding. Given the REST API, When a challenge is required, Then responses use HTTP 403 with error_code=challenge_required, include retry_after or challenge_url and an idempotency key echo, and messages are localized per Accept-Language. Given a client completes the challenge, When the next API call is made with the same idempotency key, Then the server returns HTTP 200 with normal data within one retry attempt.
Challenge Outcome Logging and Threshold Retraining
Given a challenge is issued, When the user passes, fails, times out, or abandons, Then an event is recorded with fields: timestamp, anonymized_fingerprint, ip_hash, user_agent, anomaly_score_at_issue, challenge_type, result, latency_ms, and grace_window_applied. Given outcome events are accumulating, When the nightly retraining task runs, Then it exports aggregated features and updates threshold recommendations or model parameters and records the changes with versioning. Given the false-positive rate exceeds configured_target for seven consecutive days, When retrained recommendations are available, Then an alert and change proposal are surfaced to admins with roll-back capability.
Security Telemetry & Admin Controls
"As a security-conscious team lead, I want visibility and control over Jitter Guard settings and incidents so that I can balance protection with user experience for my workspace."
Description

Deliver an admin console with live metrics and historical analytics for Jitter Guard: request rates, anomaly scores, throttle/challenge events, pass/fail rates, top offending ASNs, and cache hit ratios. Provide per-tenant configuration of jitter amplitude, cache TTLs/buckets, anomaly thresholds, and allow/deny lists with role-based access controls and audit trails. Include alerting (email/webhook) for spikes and automated recommendations to adjust settings. Export logs to SIEM via webhooks or OpenTelemetry. Ensure privacy by redacting PII and aggregating sensitive fields.

Acceptance Criteria
Live Telemetry Dashboard Accuracy and Freshness
- Given a tenant has active traffic, When an admin opens the Jitter Guard dashboard, Then the following live metrics are visible: request rate, anomaly score, throttle events, challenge events, pass rate, fail rate, top offending ASNs, cache hit ratio. - Given live view is enabled, When metrics stream, Then median data freshness is ≤ 5 seconds and P95 freshness is ≤ 10 seconds. - Given a selected time window (Last 1m or 5m), When switching windows, Then values re-compute within 2 seconds. - Given internal counters for the same period, When sampled over 1 minute, Then displayed totals are within ±2% or ±10 events (whichever is larger). - Given top offending ASNs, When displayed, Then each entry includes ASN number, name (if resolvable), and event count, sorted descending by count. - Given tenant isolation, When a user belongs to Tenant A, Then no metrics from Tenant B are visible.
Historical Analytics Coverage and Retention
- Given a date range is selected, When any range within the last 90 days is chosen, Then charts render for all metrics with hourly aggregation for ranges > 24h and minutely aggregation for ranges ≤ 24h. - Given a query for 30 days of data, When executed, Then initial results return within 5 seconds for a tenant with ≤ 10M events/day. - Given a data gap exists, When visualizing the range, Then gaps are indicated and not silently interpolated. - Given an export request, When exporting CSV for the selected range, Then the file contains aggregated values with ISO8601 UTC timestamps and column headers matching metric names. - Given period comparison is enabled, When comparing two ranges, Then percent change is shown per metric and computed to within 0.1 percentage points.
Per-Tenant Configuration Management with RBAC and Audit Trail
- Given role permissions, When a user is Admin, Then they can create, read, update, and revert Jitter Guard settings for their tenant; When a user is Analyst, Then they have read-only access; When a user lacks access, Then settings are neither viewable nor editable. - Given validation rules, When updating jitter amplitude, Then allowed range is 0–500 ms with 1 ms granularity; values outside range are blocked with an error message. - Given cache settings, When updating cache TTLs and bucket keys, Then TTL range is 10s–7d and bucket keys accept only whitelisted dimensions (e.g., path, method, region); invalid keys are rejected with reasons. - Given anomaly thresholds, When Admin sets numeric thresholds, Then changes take effect within 60 seconds and are versioned. - Given allow/deny lists, When adding CIDRs or ASNs, Then entries are validated, normalized, and enforced within 60 seconds. - Given concurrent edits, When two users save changes, Then optimistic concurrency via ETag prevents overwrites and prompts the later editor to refresh. - Given auditing, When any change is saved, Then an immutable audit record is created including actor, timestamp, masked IP, before/after diff, and reason; audit entries are filterable and exportable.
Alerting for Anomaly Spikes via Email and Webhook
- Given alert rules per tenant, When anomaly score or throttle/challenge rate exceeds configured thresholds for N consecutive minutes (configurable), Then an alert is triggered. - Given an alert is triggered, When notifications are sent, Then email is delivered to configured recipients and webhook POST is delivered within 60 seconds. - Given webhook delivery failure, When the receiver is unavailable, Then retries occur with exponential backoff for up to 24 hours; each attempt is HMAC-signed with a tenant secret and includes an idempotency key. - Given sustained breaches, When conditions persist, Then notifications are rate-limited to at most one every 15 minutes per rule and a single resolved notification is sent when metrics recover. - Given alert management, When a user acknowledges or silences a rule, Then notifications pause for the configured duration and the action is recorded in the audit log.
Automated Recommendations Generation and Application
- Given telemetry patterns indicative of scraping or misconfiguration, When heuristics or ML thresholds are met, Then a recommendation is generated suggesting changes (e.g., jitter amplitude, cache TTL, anomaly thresholds, allow/deny entries). - Given a recommendation exists, When viewed, Then it includes rationale, impacted metrics, predicted effect with confidence interval, and a diff preview of settings. - Given permissions, When an Admin applies a recommendation, Then changes are applied atomically, versioned, audited, and can be rolled back to the prior version. - Given safety limits, When a recommendation exceeds safe bounds, Then it cannot be applied and the UI explains why. - Given dismissal, When a user dismisses a recommendation, Then it is suppressed for the same trigger pattern for at least 7 days and the dismissal is audited.
SIEM Export via Webhooks and OpenTelemetry
- Given export is enabled per tenant, When configured, Then the system streams logs/metrics via (a) generic HTTPS webhook (JSON) and (b) OpenTelemetry OTLP/HTTP to the specified endpoint. - Given events are emitted, When exporting, Then at-least-once delivery is ensured with batching (max 500 records or 1 MB or 10s) and retries up to 24 hours; each event carries an idempotency key. - Given schema requirements, When payloads are sent, Then fields include timestamp (ISO8601), tenant ID, metric/event type, counts/scores, throttle/challenge outcomes, ASN, and cache metrics; PII is redacted per privacy rules. - Given downstream backpressure, When receiver responds with 429/5xx, Then the exporter backs off and does not exceed the configured max QPS. - Given connection testing, When an admin sends a test event, Then a signed test payload is delivered and success/failure is shown in the UI.
Privacy Redaction and Sensitive Field Aggregation
- Given PII handling requirements, When rendering UI metrics or exporting logs, Then end-user identifiers (emails, phone numbers, user IDs), full IP addresses, and full request paths are redacted or tokenized; only aggregated forms (e.g., ASN, /path/* buckets, /16 networks) are exposed. - Given redaction enforcement, When scanning UI and exported payloads, Then no raw PII patterns (e.g., email regex, full IPv4/IPv6) are present. - Given configuration constraints, When privacy mode is enforced by default, Then admins cannot disable redaction but can select aggregation levels; attempts to access raw PII are denied and audited. - Given compliance reporting, When an audit report is generated, Then it lists active redaction rules and includes sample payloads showing applied transformations.
Side-Channel Hardening & UX Consistency
"As a product owner, I want the anti-scraping measures to avoid leaking signals and to keep the interface predictable so that users trust the schedule they see while bots gain no extra clues."
Description

Normalize response sizes, headers, and timing across similar requests to reduce side-channel leakage that could reveal exact boundaries or internal decisions (e.g., padding responses, consistent error bodies, disabling overly specific error codes). Randomize non-critical ordering (e.g., slot lists) while preserving human-readable sorting. Enforce minimum visible window widths and 5-minute grid alignment to keep the UI legible despite jitter. Ensure booking APIs, ICS files, and notifications remain canonical and consistent with server rules. Conduct red-team style tests for binary-search attacks on boundaries and add mitigations for HEAD/OPTIONS probing.

Acceptance Criteria
Normalized Response Size, Header Set, and Timing Banding
Given a request class (endpoint and normalized params) for read operations, When 500 identical requests are executed within 5 minutes, Then the Content-Length of all 2xx responses is padded to the next multiple of 2048 bytes and varies by no more than 64 bytes across the set. Given the same test, When responses are inspected, Then the response header set and order are identical across responses and exclude server/version and debug headers. Given the same test, When JSON bodies are compared, Then object key ordering is canonicalized and field presence is constant for identical inputs. Given the same test, When response latencies are measured, Then P95 latency falls within 200–260 ms and no response is returned faster than 180 ms. Given invalid client inputs of different types to the same endpoint, When responses are returned, Then status codes are limited to 400/401/403/404/429/5xx and error bodies follow a fixed schema with generic error codes and no stack traces.
Randomized Non-Critical Ordering with Human-Readable Sort
Given a slot list response with multiple items sharing the same 5-minute start bin, When the list is generated, Then items are sorted by start time ascending and items within the same 5-minute bin are randomly ordered using a server seed. Given the same endpoint, When 10 calls are made with different seeds, Then at least 6 calls produce a different intra-bin ordering. Given the same data, When exporting via ICS or booking API, Then items are emitted in strict chronological order (no randomization) regardless of UI ordering. Given pagination or filters applied, When results are returned, Then randomization is stable within a single response and does not move items across pages.
Minimum Visible Window Widths and 5-Minute Grid Alignment
Given availability windows of any duration, When rendered in the timeline UI, Then no visible window renders narrower than 10 minutes and any underlying window shorter than 10 minutes is visually expanded without altering server-side booking rules. Given timeline rendering at any zoom level, When boundaries are drawn, Then all visible start/end boundaries snap to 5-minute increments. Given user interactions (drag, resize, select), When the user adjusts a window, Then the selection snaps to the nearest 5-minute grid line and never produces a visible window under 10 minutes. Given jitter is applied to boundary presentation, When the UI re-renders, Then no visual boundary is displayed at a non-5-minute mark.
Canonical Consistency Across API, ICS, and Notifications
Given a booking created via the API that satisfies server rules, When the booking is confirmed, Then the booking API response, ICS entry, and notification payloads contain identical start/end timestamps (to the minute) and identical time zone normalization. Given the same booking, When canonical payloads are normalized (sorted keys and stripped volatile fields), Then their SHA-256 hashes match across API response, ICS, and notification templates. Given any UI jitter affecting visual boundaries, When the canonical artifacts are generated, Then canonical start/end times remain unchanged and aligned to 5-minute increments. Given an update to an existing booking, When ICS is regenerated, Then the UID remains stable and the SEQUENCE increments by 1.
Binary-Search Boundary Attack Resilience and HEAD/OPTIONS Mitigations
Given a red-team harness issuing binary-search probes near an availability boundary at 1-minute granularity, When 100 probe requests are executed within 2 minutes, Then the attacker cannot reduce boundary uncertainty below 10 minutes based on timing, size, or code-path differences. Given the same harness, When probing patterns exceed 20 boundary-adjacent requests per minute, Then subsequent requests receive throttling (429) or a challenge within 2 requests. Given HEAD requests to availability endpoints, When responses are inspected, Then the header set/order matches GET (minus entity-specific headers), and Content-Length reports the normalized padded size used for GET. Given preflight or OPTIONS requests, When responses are inspected, Then Allow/CORS headers use fixed ordering and sizes; response latency P95 is within 200–260 ms, leaking no method-specific computation time.
Suspicious Pattern Throttling Without Impacting Humans
Given production-like traffic from human users, When anomaly detection is enabled over 7 days, Then fewer than 0.5% of sessions are flagged or throttled (false positive rate < 0.5%). Given scripted scraping traffic using high-frequency parameter sweeps or entropy-reducing probes, When observed for 2 minutes, Then 95% of offending sessions are flagged and throttled or challenged within 30 seconds of onset. Given normalized cache keys for read endpoints, When identical parameter classes are requested repeatedly over 10 minutes, Then cache hit rate is at least 80% and response sizes/timing remain within the normalization bands. Given throttling or challenges are applied, When a compliant human user returns to normal rates, Then throttles are lifted within 60 seconds and no persistent penalties remain.

Safe Propose

Let recipients one-click propose times inside open windows. Timeglue confirms exact slots on your calendar and returns holds/invites without revealing precise availability, cutting back-and-forth while preventing oversharing.

Requirements

One-click Proposal Inside Windows
"As an invitee, I want to quickly pick a time inside the host’s acceptable windows so that I can propose a meeting without time zone math or back-and-forth."
Description

Provide a recipient-facing scheduling view that displays organizer-defined open windows as selectable blocks without exposing granular free/busy. Auto-detect the recipient’s time zone, render times in local format, and support variable meeting durations defined by the organizer. Enable one-tap selection on web and mobile, enforce window boundaries and minimum/maximum lead time, and validate that proposed times respect work hours, holidays, focus blocks, and buffers. Limit the number of concurrent proposals per recipient, support accessibility (keyboard navigation, ARIA), and localize labels and date formats. Integrate with existing Timeglue smart links and draggable timeline windows so recipients can seamlessly propose within the allowed ranges.

Acceptance Criteria
Non-Granular Open Window Selection
Given a recipient opens a Timeglue smart link with organizer-defined open windows When the scheduling view renders Then only the open windows appear as selectable blocks without displaying granular free/busy slots And no heatmap, event titles, or per-minute availability are shown in the UI or exposed via accessible names or tooltips And block labels show only window start–end in the recipient’s local time And clicking an enabled block proceeds to duration selection or proposes the default duration without revealing internal availability
Time Zone Auto-Detect with Local Formatting
Given a recipient opens the scheduling view from any region When the page loads Then the system auto-detects the recipient’s time zone via browser APIs and renders all times in that zone And date/time formats follow the recipient locale (12/24h, month/day order) and labels are localized to the detected or chosen language And the recipient can change the time zone via a control, after which all times re-render consistently And the chosen time zone and locale persist for the link for at least 30 days or until cleared And server-side validation uses the selected zone to evaluate proposal timing
Variable Meeting Duration Support
Given the organizer has configured allowed durations (e.g., 15/30/45/60 minutes) and a default duration When the recipient views a window Then only start times that allow the full selected duration to finish within the window are enabled And the recipient can change duration and the available start times update accordingly And proposals include the chosen duration in the payload And durations not permitted by the organizer are not displayed or accepted by the API (400 with error code DURATION_NOT_ALLOWED)
One-Tap Selection on Web and Mobile (Accessible)
Given an enabled start time is available When the recipient taps/clicks it on mobile or desktop Then the proposal is submitted or a single confirmation action is presented, requiring at most one additional tap to submit And tap/click targets are at least 44x44 CSS pixels on touch devices And full keyboard navigation is supported (Tab/Shift+Tab to move groups, Arrow keys to move within grids, Enter/Space to select) And interactive elements have appropriate ARIA roles/states and accessible names that include date, time, and duration And focus is visible and announced; status messages use aria-live for success/error And UI meets WCAG 2.1 AA color contrast for interactive elements
Window Boundaries and Lead Time Enforcement
Given organizer-defined window boundaries, minimum lead time, maximum scheduling horizon, and slot granularity When the recipient attempts to select a time Then selections that start before the window start or end after the window end are disabled and not submit-able And selections within min lead time (e.g., < 2 hours from now) or beyond max horizon (e.g., > 60 days) are disabled and rejected server-side with 400 (error codes LEAD_TIME_VIOLATION or HORIZON_VIOLATION) And the UI displays inline, localized error messages without revealing internal availability And all accepted proposals align to the organizer’s slot granularity
Work Hours, Holidays, Focus Blocks, and Buffers Validation
Given the organizer has configured work hours, holiday calendars, focus blocks, and pre/post buffers When available start times are computed for each window Then any start time violating these constraints is excluded from the selectable options And proposals are revalidated server-side against the same rules, including buffer adjacency to existing events/holds And DST transitions and calendar exceptions are handled so that no invalid local times are offered And UI and API produce consistent results for the same inputs
Concurrent Proposal Limits and Smart Link Integration
Given an organizer-defined max concurrent proposals per recipient (N, default 3) and a valid Timeglue smart link tied to a draggable timeline When a recipient with K active proposals (pending/held) attempts to submit a new proposal Then if K >= N the submission is blocked with a localized message and a 409 response (error code PROPOSAL_LIMIT_REACHED) And if K < N the proposal is accepted and associated with the recipient identity (email or link token) without exposing organizer availability And the smart link resolves to the current timeline windows; changes to the organizer’s windows reflect on refresh without stale options And expired or unauthorized links return 410/403 respectively without leaking window details
Real-time Calendar Confirmation & Holds
"As an organizer, I want proposed times to automatically place conflict-free holds on my calendar so that meetings get secured without manual intervention."
Description

Upon proposal, perform a real-time availability check across the organizer’s connected calendars, applying Timeglue rules (work hours, focus blocks, holidays, buffers). Atomically select an exact slot within the chosen window and place a tentative hold with a unique identifier to prevent double-booking. Support Google and Microsoft calendars, multi-calendar selection, and resource calendars. Define auto-approval rules (e.g., known contacts, meeting type) to convert holds to confirmed invites, or route for manual approval. Provide immediate success/failure responses to the recipient and update the organizer’s calendar with a hold description, attendees, and metadata. Ensure idempotency and retry safety for network interruptions.

Acceptance Criteria
Atomic real-time hold placement
Given a recipient clicks Propose on a Safe Propose link for a specific open window And the organizer has Google and/or Microsoft calendars connected with selected calendars configured And Timeglue rules for work hours, focus blocks, holidays, and buffers are enabled When the proposal is received Then the system resolves availability in real time across the selected calendars, excluding blocked times per rules And selects exactly one start time within the proposed window that satisfies all rules And creates a tentative hold on the organizer’s target calendar with a unique holdId And the selected time range is marked busy so that subsequent proposals cannot be confirmed into the same slot And the operation is atomic: if any write fails, no hold persists and a failure response is returned to the recipient
Rule enforcement within windows
Given the proposed window overlaps rule-violating times (outside work hours, within focus blocks or holidays, or violates pre/post buffer) When selection is attempted Then no hold is created in any violating slot And the recipient receives an error with a machine-readable reason code describing the violated rule And if multiple compliant slots exist, the earliest compliant slot by start time is selected deterministically
Auto-approval conversion for known contacts
Given auto-approval rules are configured (e.g., known contacts, meeting type) And a tentative hold is created that matches an auto-approval rule When rules are evaluated Then the hold is converted to a confirmed event without manual action And calendar invites are sent to all attendees within 60 seconds And the original holdId is preserved as metadata on the confirmed event And the recipient’s response indicates confirmed status
Manual approval with pending status
Given no auto-approval rule matches the proposal When the hold is created Then the organizer receives an approval request via in-app notification and email with hold details (time, attendees, meeting type, holdId) And the event remains tentative until approval/decline or until the configurable expiration time elapses And the recipient is immediately informed that the proposal is pending approval And upon decline, the hold is removed and the recipient is notified with a reason code
Immediate recipient feedback and calendar update
Given successful hold placement When the operation completes Then the recipient receives a success response within 3 seconds including selected start/end times and a reference token And the organizer’s calendar shows a tentative event with title, holdId, meeting type, recipient name/email, and Timeglue metadata in the description And the event includes organizer and recipient as attendees and is marked tentative/busy according to configuration
Idempotent processing and retry safety
Given the client retries the same proposal due to a network interruption using the same idempotency key within 24 hours When the server processes the retry Then no additional holds are created And the original holdId and selected time are returned with the same status as the initial response And if the initial attempt completed but the response failed, the retry returns the original success; if it did not complete, the retry performs the operation once
Multi-calendar and resource calendar support
Given the organizer has selected multiple personal calendars and one or more resource calendars for availability checks When placing a hold Then availability is computed as the intersection of free time across all selected calendars with rules applied And the hold is created on the primary target calendar and corresponding resource holds are created on required resource calendars And if any required resource cannot be held, the transaction aborts and a failure response is returned; no partial holds remain And all created events include the same holdId and correlation metadata
Availability Privacy Masking
"As a privacy-conscious organizer, I want my schedule details hidden so that invitees can propose times without learning my exact availability."
Description

Ensure recipients never see precise free/busy data by only rendering organizer-defined open windows and abstracted time ranges. Prevent inference attacks by avoiding granular edge disclosures, applying optional slot-edge rounding, throttling repeated probes, and suppressing error messages that reveal availability details. Redact sensitive content from notifications, ICS descriptions, and API responses. Offer configurable privacy levels (standard, strict) and provide audit logs for access. Comply with organizational privacy policies while maintaining a smooth proposal experience.

Acceptance Criteria
Render Only Abstracted Open Windows
- Given a recipient opens a Safe Propose link, When the scheduler renders availability, Then only organizer-defined open windows are shown as opaque blocks without revealing precise free/busy events or gaps. - And Then start/end clock times are displayed as rounded ranges per privacy level, not exact internal edges. - And Then no event titles, counts, durations, or gap sizes are shown or inferable from UI affordances (hover, tooltips, keyboard focus, screen reader labels). - And Then navigating dates or changing time zones preserves the abstraction without exposing new granularity.
Slot-Edge Rounding Controls
- Given privacy level is Standard, When an open window’s internal edges fall on non-increment times, Then the UI displays window edges rounded outward to the nearest 15-minute boundaries. - Given privacy level is Strict, When rendering the same window, Then edges are rounded outward to the nearest 30-minute boundaries and any displayed gap between windows is never less than 30 minutes. - And Then proposals are constrained to real underlying availability; when a user selects within a visually rounded area that exceeds real availability, the selection snaps inward silently without disclosing which edge was constrained.
Probe Throttling and Rate Limits
- Given a recipient or client repeatedly queries availability for a link, When more than 20 availability fetches occur within 60 minutes per IP+recipient+link, Then subsequent requests are throttled with HTTP 429 and the generic message "Temporarily unavailable. Please try again later." - And Then the throttle resets after 60 minutes and is captured in audit logs. - Given burst traffic exceeds 5 requests within 5 seconds, Then exponential backoff is applied (initial delay ≥1s, max delay ≤30s) uniformly, without varying based on availability state.
Error Message Neutrality
- Given a recipient attempts to propose a time that conflicts with organizer availability, When the proposal is rejected, Then the UI and API return a generic reason "Time unavailable" without referencing event titles, counts, specific start/end times, or calendar names. - Given a link is expired or the organizer’s calendar is disconnected, When the recipient loads the page, Then a generic status is shown (e.g., "Link unavailable") without indicating whether the cause is calendar content or configuration. - And Then HTTP status codes and response shapes remain consistent across error types so as not to imply availability details.
Redaction in Notifications, ICS, and API
- Given a hold is placed via Safe Propose, When ICS files are generated, Then SUMMARY is "Hold — Timeglue" and DESCRIPTION includes only the proposal link and generic instructions; no organizer event titles, attendees, or free/busy details are included. - And Then API responses for availability endpoints exclude raw free/busy arrays and return only abstracted windows (start, end, privacyLevel) with edges truncated per rounding rules. - And Then notifications (email/push) sent to recipients and organizers include only proposed slot times and the link; no disclosure of why other times were unavailable.
Configurable Privacy Levels and Policy Enforcement
- Given an organization sets default privacy level to Strict, When new Safe Propose links are created, Then they inherit Strict by default unless the creator explicitly overrides and has permission per policy. - Given a policy forbids Standard level, When a creator attempts to select Standard, Then the UI prevents the selection and the API returns a policy error that contains no availability details. - And Then the effective privacy level per link is visible to the organizer in settings and recorded in audit logs.
Audit Logging and Access
- Given any recipient views a Safe Propose link or submits a proposal, When the action occurs, Then an immutable audit entry is written with timestamp (UTC), link ID, recipient pseudonymous ID, truncated IP (/24 or /64), action type, outcome, and effective privacy level. - And Then admins with Audit permission can filter, search, and export entries (CSV and JSON) without any raw availability or event content. - And Then log retention follows org policy (configurable 90–365 days) and deletion jobs are verifiable by expected-count metrics.
Conflict Handling & Smart Alternatives
"As an invitee, I want clear alternatives when a time becomes unavailable so that I can quickly choose another slot without starting over."
Description

Detect race conditions and conflicts caused by simultaneous submissions or late calendar changes. If a hold cannot be placed, instantly suggest the nearest viable alternatives within the same windows that meet duration and buffer rules. Support first-come, first-served locking with transactional consistency, a brief grace period queue, and optional auto-shift within the window. Provide clear UX to reselect, and preserve prior form inputs. Log outcomes for analytics (drop-off, conversion) and expose webhooks/events for downstream workflows.

Acceptance Criteria
Simultaneous Proposals: First-Come, First-Served Locking
Given an open 30-minute window with 10-minute pre/post buffer rules And two recipients submit the exact same start time within 2 seconds When Timeglue processes both proposals concurrently Then exactly one proposal is granted a hold with hold_id and expires_at set to the organizer-configured hold_ttl_minutes (default 15) And the other receives a 409 conflict response with code "CONFLICT_LOCKED" and a prompt to reselect without revealing exact availability And no double-booking occurs in either Timeglue or the connected calendar provider
Transactional Consistency and Idempotency Across Providers
Given an idempotency_key is provided with a proposal request And the connected calendar is Google or Microsoft 365/Outlook When transient network errors or client retries occur within 60 seconds using the same idempotency_key Then Timeglue creates at most one hold and subsequent identical requests return the same outcome and hold_id And if the provider precondition (ETag/If-Match) fails indicating a race, Timeglue aborts and returns a conflict with suggestions And no orphaned holds remain if the provider rejects the hold; any partial local state is rolled back
Grace Period Queue with Timeout and Fairness
Given multiple proposals contend for the same slot When the first hold is granted Then subsequent proposals are placed into a FIFO grace queue for up to 15 seconds or until the hold is confirmed/expired And if the initial hold is confirmed within the grace period, queued proposals are rejected with conflict and receive alternatives And if the initial hold expires unconfirmed, the next proposal in queue is processed atomically And the queue enforces a maximum of 10 waiting proposals; additional proposals receive immediate alternatives
Smart Alternative Suggestions Within Same Windows
Given a proposal cannot be held due to a conflict or late calendar change When generating alternatives Then Timeglue returns up to 5 nearest viable times within the same published windows that satisfy duration and buffer rules And suggestions exclude times outside work hours, holidays, and focus blocks And suggestions are sorted by absolute proximity (minutes) to the requested time, including at least one earlier and one later option when available And alternative generation latency is <= 500 ms at p95
Optional Auto-Shift to Nearest Viable Slot
Given the organizer has enabled auto_shift with max_auto_shift_minutes set (e.g., 10) And a requested time is invalid due to a new conflict but there is a viable time within the same window When the nearest viable start within max_auto_shift_minutes exists and satisfies duration and buffer rules Then Timeglue automatically places a hold at the shifted time and returns auto_shifted=true with shifted_by_minutes and the new start/end And if no viable shift exists within the limit, Timeglue skips auto-shift and returns alternative suggestions
UX Reselect with State Preservation and Accessibility
Given a conflict prevents the requested hold from being placed When the recipient is shown the reselect view Then previously entered inputs (name, email, notes, custom questions, selected timezone, duration) are preserved and pre-filled And the UI presents generic conflict messaging and suggestions without exposing exact availability And keyboard focus moves to the reselect heading and the flow meets WCAG 2.1 AA focus and contrast requirements And refreshing or navigating back within 10 minutes restores the preserved inputs and suggestions
Outcome Logging and Webhooks for Downstream Workflows
Given any proposal attempt completes with an outcome (hold_placed, conflict, auto_shifted, suggestion_shown, booked, drop_off) When the outcome is determined Then Timeglue logs an analytics event with request_id, recipient_id (if available), organizer_id, window_id, slot_start/end, outcome, reason_code, and latency_ms And a webhook is delivered within 2 seconds (p95) with the same payload and an HMAC-SHA256 signature header; delivery is at-least-once with exponential backoff for up to 24 hours And webhook retries stop after a 2xx response; non-2xx responses are retried; payloads never include raw free/busy details
Notifications, Invites, and Updates
"As an organizer, I want automated notifications and invites so that everyone stays aligned without manual coordination."
Description

Send timely notifications to organizers and recipients when proposals are submitted, holds placed, accepted, rescheduled, or canceled. Include branded emails, Slack notifications, and ICS attachments for tentative holds and confirmed events. Respect user notification preferences and quiet hours. Update or cancel calendar holds when changes occur, maintain RSVP status, and send reminders before the meeting. Provide links to manage the proposal, add agenda notes, or cancel gracefully with automatic release of holds.

Acceptance Criteria
Real-time Notifications on Proposal Lifecycle Events
Given an organizer with email and Slack notifications enabled and outside quiet hours When a recipient submits a proposal via a Safe Propose link Then the organizer receives a branded email within 60 seconds containing the meeting title, proposer identity (name or email), proposed slot(s) in organizer’s timezone, and a Manage Proposal link And the organizer receives a Slack notification to the configured destination within 60 seconds with the same summary and link And no more than one notification per channel is sent per event (no duplicates) Given an active proposal with holds enabled When a hold is placed, accepted, rescheduled, or canceled Then the organizer and affected recipients receive channel-specific notifications within 60 seconds of the event reflecting the new state (held, confirmed, rescheduled with old→new time, or canceled) And each notification includes a persistent Proposal ID/reference and Manage link And notification delivery outcomes are logged with timestamp and channel
Respect Notification Preferences and Quiet Hours
Given a user has disabled Slack notifications and enabled email When any proposal lifecycle event occurs Then only email is sent and no Slack messages are delivered Given a user has quiet hours set from 22:00 to 07:00 local time When an event-triggering action occurs at 23:00 Then notifications are queued and delivered at or after 07:00 local time And the queued notifications retain correct timestamps and links Given quiet hours would defer delivery past the scheduled meeting start time When the deferment is calculated Then the system suppresses the notification and records the suppression with reason "Quiet hours exceeded start time"
ICS Attachments for Holds and Confirmations
Given a hold is placed on a proposed slot When notifications are sent to organizer and recipients via email Then the email includes an .ics attachment with METHOD:REQUEST, STATUS:TENTATIVE, and a unique UID And the .ics times reflect the correct timezone (VTIMEZONE included or UTC with Z) and duration Given a proposal is accepted and the event is confirmed When confirmation emails are sent Then the .ics attachment reuses the same UID as the hold, increments SEQUENCE, and sets STATUS:CONFIRMED And opening the .ics in Google Calendar and Outlook updates the existing tentative entry rather than creating a duplicate Given a hold is canceled When a cancellation email is sent Then an .ics with METHOD:CANCEL using the same UID is included so calendars remove the tentative hold
Calendar Holds Updated or Released on Changes
Given an organizer’s primary calendar is connected When a hold is placed for a proposed slot Then a single tentative event is created on the organizer’s calendar within 60 seconds with the correct start/end and title pattern "[Hold] <Meeting Title>" Given an existing hold with UID H1 When the proposal is rescheduled to a new time Then the calendar event with UID H1 is updated in place to the new time without creating additional events Given an existing hold with UID H1 When the proposal is canceled Then the calendar event with UID H1 is removed or marked canceled within 60 seconds, freeing the time slot Given a hold transitions to a confirmed event When acceptance occurs Then the tentative event is converted to confirmed in place and the title is updated to remove the hold marker
Maintain RSVP Status Through Changes
Given a recipient has RSVP’d "Accepted" to a confirmed event When the event is rescheduled using the same UID (update via SEQUENCE increment) Then the recipient’s RSVP remains "Accepted" in Timeglue and in the calendar attendee list Given a recipient has RSVP’d "Declined" When minor metadata changes occur (title, agenda link) without time change Then the RSVP status remains "Declined" Given an event is canceled When cancellation is processed Then all attendee RSVP statuses are recorded as "Canceled" in the audit log and no further reminders are sent
Pre-Meeting Reminder Delivery
Given a confirmed event scheduled for the future and default reminders set to 24 hours and 15 minutes before start When the reminder times are reached Then organizer and recipients receive reminders via enabled channels with meeting title, start time in their local timezone, join/location info, and Manage link And no reminders are sent for canceled or superseded events Given a user has quiet hours that overlap a reminder time When the reminder would be sent during quiet hours Then the reminder is deferred until quiet hours end if that time is still before event start; otherwise it is suppressed And the deferral or suppression is logged with reason
Manage, Agenda, and Graceful Cancel Links in Messages
Given a notification (email or Slack) is sent for any proposal lifecycle event When the recipient views the message Then the message contains working links to Manage Proposal, Add/Edit Agenda, and Cancel Meeting Given the recipient clicks Cancel Meeting When the confirmation is accepted Then all related holds on the organizer’s calendar are released within 60 seconds And cancellation notifications are sent to all participants via enabled channels Given a manage/cancel link is expired or invalid When it is accessed Then the user sees an error state with no changes applied and an option to request a new link And the access attempt is logged for audit
Smart Link Security & Controls
"As a team lead, I want fine-grained control over who can propose and for how long so that links remain secure and aren’t abused."
Description

Add controls to Safe Propose links including visibility (public, invitee-only, domain-restricted), expiration dates, maximum proposals per recipient, and per-link rate limiting/CAPTCHA to curb abuse and scraping. Allow revoke/regenerate actions and immediately reflect changes to windows and rules on existing links. Track access in an audit trail with IP, user agent, and referrer; support UTM parameters for source attribution. Ensure tokens are scoped, short-lived where appropriate, and stored/validated securely server-side.

Acceptance Criteria
Enforce Visibility Modes: Public, Invitee-Only, Domain-Restricted
- Given a Safe Propose link set to Public, When any user opens the link, Then the propose-times UI renders without requiring authentication and no exact availability or free/busy details are revealed. - Given a Safe Propose link set to Invitee-Only with tokenized invitees, When a user without a valid invitee token opens the link, Then the system returns 403 with an access-gated screen and no window data is exposed. - Given a Safe Propose link set to Domain-Restricted for example.com, When a signed-in user with email user@example.com opens the link, Then access is granted; When a user with a non-matching domain opens, Then access is denied with 403 and guidance to use a permitted email. - Given domain checks, Then comparisons are case-insensitive, and only exact domain matches are allowed unless subdomains are explicitly configured on the link. - Given access is denied for any visibility control, Then an audit log entry is recorded with outcome=denied and reason=visibility.
Link Expiration and Post-Expiry Handling
- Given a Safe Propose link with an expiration timestamp, When the link is accessed after that time, Then the API responds 410 Gone and the UI indicates the link has expired with an option to contact the organizer. - Given a Safe Propose link before expiry, When a proposal submission is received, Then it is accepted only if the server time is strictly before the expiration timestamp; otherwise it is rejected with 410 and no holds are created. - Given a link expires, Then no window data is returned by the API and an audit event outcome=expired is recorded with the attempted IP and user agent. - Given the organizer extends or shortens the expiration, When the link is reloaded or the next API call occurs, Then the new expiration takes effect within 10 seconds.
Maximum Proposals Per Recipient Enforcement
- Given max_proposals_per_recipient=N on a Safe Propose link, When a recipient identified by their invitee token submits proposals, Then the system increments their count for each successfully created hold. - Given the recipient’s count reaches N, When they attempt any additional proposal, Then the request is rejected with 429 and a message indicating the limit has been reached, and an audit log entry outcome=rate_limited is recorded. - Given multiple devices or browsers are used by the same recipient, Then the limit is enforced consistently across all devices and sessions. - Given the organizer resets a recipient’s counter, When the recipient submits again, Then proposals are accepted up to the configured limit.
Per-Link Rate Limiting and CAPTCHA
- Given a Safe Propose link with rate_limit=R per T seconds per IP, When requests exceed R within T, Then the API returns 429 and the UI presents a CAPTCHA challenge. - Given the user solves the CAPTCHA successfully, When the next request is made, Then it is accepted and the rate-limit window resets for that IP. - Given repeated CAPTCHA failures M times, Then the IP is temporarily blocked for B minutes per configuration and further requests are denied with 429. - Given a signed-in, verified invitee, Then CAPTCHA is not shown unless abuse is detected as per configuration thresholds. - Given rate limiting is triggered, Then an audit log entry is recorded with reason=rate_limit and includes IP and link_id.
Revoke and Regenerate Link Tokens
- Given an organizer clicks Revoke on a Safe Propose link, When any user attempts to access the revoked link, Then the API returns 410 and no window data is exposed. - Given an organizer clicks Regenerate on a Safe Propose link, Then a new token is created, the old token is invalidated immediately, and the share URL updates. - Given there are existing holds created via the old token, Then those holds remain on the organizer’s calendar and are not canceled by revoke/regenerate. - Given a revoke or regenerate action occurs, Then an audit trail entry is created with actor, timestamp, action, and link_id.
Immediate Reflection of Window and Rule Updates on Existing Links
- Given an organizer updates availability windows or link rules (visibility, expiration, limits), When an existing Safe Propose link is reloaded or an API request is made, Then the new windows and rules are applied within 10 seconds without requiring a new link. - Given a user is viewing the link at the time of change, Then the UI refreshes available times within 10 seconds and prevents selection of slots that were removed. - Given a selection is submitted after rules changed to make it invalid, Then the server rejects the submission with 409 and returns the updated rules and windows to the client.
Access Audit Trail and UTM Attribution
- Given any access to a Safe Propose link, Then an audit record is stored with timestamp (UTC), link_id, actor (if known), IP address, user agent, referrer, and UTM parameters (utm_source, utm_medium, utm_campaign, utm_term, utm_content) when present. - Given an access is denied due to visibility, expiration, rate limiting, or revoke, Then the audit record outcome reflects the denial reason and includes the relevant rule that triggered it. - Given analytics queries are run, Then audit records can be filtered by UTM parameters and aggregated by source to attribute traffic and conversions. - Given data retention policy of D days, Then audit records older than D days are purged per configuration and purge events are logged.
Secure Token Scope, Lifetime, and Storage
- Given a Safe Propose link token is generated, Then it is scoped to link_id and permissions, contains no PII, and is signed; only a hashed/HMAC form is stored server-side. - Given a token is presented, Then it is validated server-side only, checked for expiration/rotation/revocation, and rejected if invalid with 401 without revealing which check failed. - Given a proposal submission, Then a per-request nonce is required and validated to prevent replay; tokens and nonces are not logged in plaintext in any logs. - Given a viewer session is idle for S minutes (configurable), Then any short-lived session token expires and re-authentication is required where applicable.

Ghost Ledger

Maintain a privacy-first audit trail of who accessed which link, at what trust tier, and what actions they took. Exportable logs demonstrate compliance and build internal trust that masked sharing protects sensitive schedules.

Requirements

Append-only Access Event Ledger
"As a workspace admin, I want a complete, privacy-preserving record of link accesses and actions so that I can reconstruct who did what and when without exposing sensitive schedule details."
Description

Implement an append-only ledger that records every access to a Timeglue smart link and every subsequent action (view, propose, book, reschedule, decline, add-to-calendar) with precise timestamps, link identifiers, workspace, pseudonymous actor ID, resolved trust tier, request metadata (IP region, user agent), and outcome codes. Events are written asynchronously to a durable, write-optimized store with idempotency keys, backpressure, and retry logic to ensure completeness without impacting link performance. Personally identifiable data is never stored in cleartext; use salted hashing and field-level redaction aligned with Ghost Ledger privacy objectives. The ledger integrates with existing link and booking services via an event bus, supports high volume from shared links, and exposes a consistent schema for downstream querying and export.

Acceptance Criteria
Append-only immutability enforcement
- Ledger storage allows only INSERT operations; UPDATE and DELETE operations are disabled at the service layer and rejected with 409 Conflict - Any attempt to modify an existing event (by event_id or unique idempotency_key) results in no change to persisted data and an audit of the rejected attempt - Logical corrections are recorded as new append-only events; no existing row values are altered - Storage is configured as append-only (e.g., WORM/immutable logs) and verified by a write-then-modify attempt that leaves row count and checksums unchanged
Idempotent ingestion prevents duplicates
- Each submitted event must include an idempotency_key; requests without it are rejected with 400 Bad Request - Multiple submissions with the same idempotency_key within a 24-hour window persist exactly one event and return the original event_id on subsequent calls - Replayed events with identical idempotency_key and differing payloads do not create new events; the first accepted payload remains authoritative - Idempotency window and key uniqueness are enforced per workspace_id to prevent cross-tenant collision
Privacy-preserving hashing and redaction
- No personally identifiable information (PII) is stored in cleartext; emails, user identifiers, and IP addresses are not persisted as raw values - Pseudonymous actor identifiers are stored as salted, non-reversible hashes; the salt is managed via a rotation version recorded with each event - Only IP region (e.g., country/subdivision) is persisted; full IP is neither stored nor exportable - User agent strings are persisted as-is; any fields carrying potential PII (e.g., query params) are redacted according to the Ghost Ledger policy - All export endpoints return only redacted/hashed fields; exporting cleartext PII is technically impossible
Asynchronous writes meet performance SLOs
- Event recording is fully asynchronous; user-facing link responses do not await durable write completion - Added request latency from enqueueing the event is < 10 ms at p95 and < 20 ms at p99 compared to baseline under nominal load - 99% of events are durably persisted within 60 seconds of enqueue; 99.9% within 5 minutes during transient outages - If the event bus/storage is unavailable, the system queues to a local durable buffer and serves the user response without error
Schema completeness and validation of required fields
- Each event includes: server_assigned_time (UTC, ms precision), event_type ∈ {view, propose, book, reschedule, decline, add_to_calendar}, link_id, workspace_id, pseudonymous_actor_id_hash, trust_tier, ip_region, user_agent, outcome_code, idempotency_key, event_version - server_assigned_time is generated at enqueue time by the service; client-supplied timestamps (if any) are stored separately and optional - Requests missing required fields or with invalid enumerations are rejected with 422 Unprocessable Entity and are not written - outcome_code and event_type use a documented enumeration; unknown values are rejected with 400 Bad Request
Backpressure and retry with at-least-once durability
- Ingestion applies backpressure when downstream latency rises, spilling to a disk-backed queue without dropping events - Retries use exponential backoff with jitter up to 15 minutes and continue for at least 72 hours or until success; on success the event appears exactly once in the ledger (idempotency_key enforced) - During sustained outages, link traffic continues to be served; no user-facing failures are caused by ledger unavailability - Dead-letter queue captures permanently failed events with reason codes; DLQ rate remains 0 under nominal load
Exportable audit logs with filters and consistent ordering
- Export API supports filtering by workspace_id, link_id, time range, event_type, and trust_tier - Results are ordered by server_assigned_time ascending, then by event_id for deterministic tie-breaking - Pagination is cursor-based and stable across pages; no event is skipped or duplicated when iterating - Exports include only redacted/hashed fields and conform to the public schema; a 1M-event export completes within 5 minutes via streaming
Trust Tier Attribution Engine
"As a workspace admin, I want each access session to be classified into a trust tier so that I can understand the exposure level of every interaction."
Description

Determine and record a trust tier for every access session using multiple signals: signed link tokens, workspace membership, SSO/OAuth identity, email verification, prior collaboration history, and network heuristics. Persist both the resolved tier (e.g., Owner, Member, Trusted External, Anonymous) and the attribution rationale. Degrade gracefully when identity is partial by using stable pseudonyms and tier fallbacks. Provide a synchronous API for link services to request tier decisions and an asynchronous stream that updates events if attribution improves during a session. Caches recent decisions to minimize latency and integrates with Timeglue’s existing sharing and permissions model.

Acceptance Criteria
Synchronous Tier Decision API
Given a link access request containing link_id, optional session_id, and signals (signed_link_token, workspace_id, sso_id, verified_email, ip, user_agent, prior_collaboration_id) When POST /v1/trust/decide is called with the signals Then the response status is 200 and includes fields: session_id, tier in {Owner, Member, Trusted External, Anonymous}, rationale as a non-empty array of reasons with signal references And p95 latency <= 150 ms and p99 <= 300 ms under 100 RPS with no more than 1% 5xx And mapping is applied in priority order: Owner (signed_link_token identifies link owner) > Member (valid workspace membership via SSO/OAuth) > Trusted External (verified_email true AND prior_collaboration_id present OR network_heuristic.trusted_asn true) > Anonymous (fallback) And invalid/expired signed_link_token yields tier=Anonymous with rationale including "invalid_token" without 5xx And for identical inputs within a session, the decision is deterministic (same tier and rationale order)
Decision Caching and TTL
Given two identical decision requests with the same identity fingerprint (normalized set of signals) within the default TTL of 5 minutes When the second request is evaluated Then the decision is served from cache with header X-Trust-Cache=HIT and identical tier and rationale And cache p95 latency <= 30 ms And changing any fingerprinted signal (e.g., sso_id, verified_email) causes X-Trust-Cache=MISS and a fresh evaluation And the TTL is configurable (1–30 minutes) and respects per-tenant overrides And cache entries are evicted when a session receives an attribution upgrade event
Asynchronous Attribution Upgrades During Session
Given a session that initially resolved to Anonymous or Trusted External And the user later completes SSO/OAuth or proves workspace membership during the same session When the new signal is received Then an event trust.tier.upgraded is published to the attribution stream within 2 seconds with payload including {session_id, link_id, previous_tier, new_tier, rationale[], occurred_at} And the new_tier strictly dominates the previous_tier in the order Owner > Member > Trusted External > Anonymous (no downgrades mid-session) And the Ghost Ledger record for the session is updated to include the upgrade with a new event_id and immutable history of prior decisions And at-least-once delivery is guaranteed with idempotency via event_id to prevent duplicate processing by consumers
Graceful Degradation with Stable Pseudonyms
Given insufficient identity to resolve above Anonymous (e.g., no valid token, no SSO, no verified_email) When a decision is made Then a stable pseudonym in the form anon_<hash> is assigned, derived from a salted hash of stable signals (e.g., cookie_id, device fingerprint, coarse IP/ASN) and remains consistent for at least 30 days while those signals remain unchanged And no raw PII (email, full IP) is embedded in or recoverable from the pseudonym And if the session later upgrades identity, the ledger retains linkage from the pseudonym to the verified identity in the audit history without exposing the hash salt And the fallback tier is Anonymous with rationale explicitly listing which signals were missing or unverified
Persistence of Tier and Attribution Rationale in Ghost Ledger
Given any decision or upgrade event When the decision is finalized Then a Ghost Ledger entry is persisted within 500 ms containing {event_id, link_id, session_id, actor_pseudonym_or_user_id, tier, rationale[], signals_used[], decided_at} And sensitive tokens (SSO/OAuth, signed_link_token) are never stored; only non-reversible references or hashes are recorded And querying the audit export for a date range returns the entry with exact fields and values matching the decision response And access to ledger records is enforced by workspace permissions; unauthorized users receive 403 and no data leakage occurs
Permissions Model Integration and Enforcement
Given a link with sharing policy that specifies minimum required tier (e.g., Members Only, Trusted External or above) When the attribution engine returns a tier for an access session Then access is granted only if the returned tier meets or exceeds the policy's minimum And attempts to access with a lower tier are denied with 403 and a ledger entry is recorded including the evaluated tier and policy that blocked access And the engine never returns a tier higher than justified by available signals (no privilege escalation) and such constraints are unit-tested with simulated adversarial inputs
Action Taxonomy and Redaction Rules
"As a security officer, I want sensitive fields to be masked in the audit trail so that we maintain privacy while still proving compliant behavior."
Description

Define a canonical action vocabulary (view_window, view_masked_details, propose_slot, book_slot, cancel, reschedule, export_ics) and a structured payload schema that captures context (link scope, time range viewed, fields touched) while enforcing field-level redaction for sensitive schedule data (exact titles, attendee lists, notes). Provide workspace-configurable redaction policies and allow per-link overrides to align with masked sharing. Apply redaction prior to persistence and export, and version policies so each event records the redaction profile used. Ensure compatibility with holidays, working hours, and focus blocks so metadata is auditable without leaking content.

Acceptance Criteria
Canonical Action Vocabulary Enforcement
Given an audit event is submitted with action not in {view_window, view_masked_details, propose_slot, book_slot, cancel, reschedule, export_ics} When the event is processed Then respond 400 InvalidAction and persist no record Given an audit event is submitted with an allowed action from the canonical set When the event is processed Then respond 201 Created and persist exactly one record with that action value Given a persisted audit record When it is retrieved Then its action is one of the canonical values and is immutable across updates Given SDKs and API clients When type bindings are generated Then only the canonical action values are accepted by compile-time and runtime validation
Structured Context Payload Captures Required Fields Without Sensitive Values
Given an allowed action event When it is persisted Then the payload includes link_id, link_scope, action, occurred_at (UTC ISO 8601), actor_id_hashed, actor_trust_tier, request_id, and, where applicable, time_range_viewed {start_utc, end_utc} and fields_touched[] Given an event includes actor identity or sensitive fields When it is persisted Then actor_id is salted-hash and fields_touched contains only field identifiers (no values) Given a view_window action with a specific time span When it is persisted Then time_range_viewed reflects the exact start and end in UTC and is absent for actions where it is not applicable Given a malformed payload (missing required fields or invalid timestamps) When submitted Then respond 400 with a machine-readable error code and persist nothing
Field-Level Redaction Applied Prior to Persistence and Export
Given a meeting with sensitive title, attendees, and notes When any related audit event is created Then the persisted payload contains only redaction indicators (e.g., title_redacted=true, attendees_count) and never raw titles, attendee names/emails, or note contents Given an audit log export is requested via API or UI When the export file is generated Then no row contains sensitive schedule values (titles, attendee identities, freeform notes) and the export passes automated PI detection checks Given a high-trust tier actor or debug mode When events are logged Then the same redaction policy is enforced and no unredacted values are persisted or exported Given a processing error occurs before redaction completes When attempting to persist Then the transaction aborts and no unredacted record is written to storage
Workspace Redaction Policies with Per-Link Overrides and Precedence
Given a workspace default redaction policy P0 and a per-link override policy P1 When an event for that link is logged Then P1 is applied and P0 is not Given a link without an override When an event is logged Then the workspace default policy is applied Given conflicting settings between system minimums, workspace policy, and link override When resolving effective policy Then precedence is link_override > workspace_default > system_minimums Given policy evaluation completes When the event is persisted Then the effective policy id and source (link or workspace) are stamped into the record
Policy Version Stamping and Non-Retroactive Changes
Given policy definitions exist at version v3 When an event is persisted Then the record includes policy_version_id=v3 and a checksum of the policy document Given policies are updated to v4 When new events are logged Then they are stamped with v4 and previously persisted records remain stamped with v3 Given an auditor inspects an event When requesting the policy by version and checksum Then the exact policy text applied at the time is retrievable and matches the checksum Given a policy is deprecated When logging events Then events referencing the deprecated version are rejected unless a migration map is provided and recorded
Constraint Compatibility Metadata Without Content Leakage
Given an action intersects holidays, working hours, or focus blocks When the event is persisted Then the payload includes boolean/enum flags (intersects_holiday, within_work_hours, intersects_focus_block) and never the names/titles of underlying events Given a view_window over a masked calendar When the event is logged Then only time_range_viewed and constraint flags are stored; no calendar titles or invitee identities are present Given a propose_slot outside recipient working hours When the event is logged Then the payload includes rule_violation_code=OUTSIDE_WORK_HOURS and captures the proposed time range without exposing any private schedule content
Audit Search and Filter Console
"As a team lead, I want to search and filter audit events by link, timeframe, user, and trust tier so that I can quickly answer stakeholder questions."
Description

Deliver an in-app Ghost Ledger console for authorized roles with fast search and filtering by date range, link ID, workspace, actor pseudonym, trust tier, action type, and outcome. Provide event timelines per link and per actor, pagination for large result sets, and deep links for sharing internal investigations. Enforce role-based access controls and workspace boundaries. Optimize queries via indexed storage and time-partitioned datasets to keep UI responsive under high event volumes. Integrate with Timeglue’s admin navigation and respect workspace time zone settings.

Acceptance Criteria
RBAC, Admin Navigation, and Workspace Boundary Enforcement
Given a user with Audit Console access in Workspace A, When they navigate via Admin > Ghost Ledger > Audit Console, Then the console is visible and loads successfully with no cross-workspace data. Given a user without Audit Console access, When they attempt to load the console route or a deep link, Then a 403 error view is shown and no events are fetched. Given a user with access to Workspace A only, When they attempt to query or deep-link to events from Workspace B, Then access is denied and zero results are returned. Given an authorized user in the console, When they use the workspace switcher to change workspaces, Then the console reloads scoped to the selected workspace and prior results are cleared. Given an authorized user, When the console loads, Then the Admin navigation highlights Ghost Ledger and the breadcrumb shows Admin > Ghost Ledger > Audit Console.
Date Range Filtering Honors Workspace Time Zone
Given the workspace time zone is set (e.g., America/Los_Angeles), When a user selects a date range [Start, End], Then the backend applies UTC boundaries computed from the workspace time zone and returns events whose timestamps fall within the inclusive range. Given a DST transition occurs within the selected range, When results are returned, Then no hour is double-counted or skipped; event counts reflect actual timestamps within the computed bounds. Given the workspace time zone setting is changed, When the user reloads the console, Then date pickers and displayed timestamps reflect the new zone and re-running the same range returns the same logical set relative to the new zone. Given an event at exactly the end boundary time in the workspace zone, When filtering, Then it is included (inclusive end).
Multi-Field Search and Filter
Given filters for Link ID (exact), Actor Pseudonym (contains, case-insensitive), Trust Tier (multi-select), Action Type (multi-select), Outcome (multi-select), When the user applies any combination with a date range, Then only events matching all applied filters are returned and the total count reflects the filtered set. Given a valid Link ID is entered, When the query runs, Then only events for that Link ID are returned. Given a partial Actor Pseudonym string is entered, When the query runs, Then events whose pseudonym contains that string (case-insensitive) are returned. Given a query returns no matching events, When results render, Then the table shows an explicit "No results" state and total = 0. Given filters are cleared, When the user resets, Then the console returns to default date range and removes all active filter tokens.
Pagination for Large Result Sets
Given more than 50 matching events, When viewing results, Then the console paginates with a default page size of 50 and displays the total count. Given pagination controls for page size (25/50/100), When the user changes the size, Then the list updates accordingly and the size is encoded in the URL. Given navigation controls (first/previous/next/last), When clicked, Then the corresponding page loads and the view scrolls to top; P95 page-change latency <= 500 ms on production-like data. Given a deep link containing page and size parameters, When opened by an authorized user, Then the same page of results is shown under the same filters. Given a request for a page beyond the last available, When executed, Then the console loads the last page and indicates the current page accurately.
Event Timeline Views per Link and per Actor
Given a Link ID in results, When the user selects "View timeline", Then a chronological timeline for that link displays with timestamp, actor pseudonym, trust tier, action type, and outcome for each event. Given an Actor Pseudonym in results, When the user opens the actor timeline, Then events across links within the current workspace are shown chronologically. Given a timeline view, When the user adjusts the date range, Then the timeline re-queries and updates without full page reload. Given multiple events share the same timestamp, When displayed, Then they are secondary-sorted by event ID (or insertion order) to ensure stable ordering. Given a timeline fetch error occurs, When handled, Then a non-PII error state is shown and no partial or cross-workspace data is exposed.
Deep Links for Internal Investigations
Given active filters, pagination, sort, and (if applicable) a timeline context, When the user clicks "Copy deep link", Then a URL is generated that encodes those parameters without exposing PII beyond pseudonyms and IDs. Given a recipient with Audit Console access in the same workspace, When they open the deep link, Then the console reproduces the exact state and result set. Given a recipient without access or from a different workspace, When they open the deep link, Then a 403 view is shown and no data is loaded. Given malformed or unsupported parameters in a deep link, When opened, Then the console falls back to safe defaults, logs a client error event (no PII), and remains functional.
Query Optimization and UI Responsiveness Under High Volume
Given a production-like dataset with >= 10 million events in the current workspace using time-partitioned storage, When running a query with any single-field filter within a 30-day range, Then first-page response time is <= 700 ms at P95 and <= 1200 ms at P99. Given combined filters across two or more fields within a 30-day range, When querying, Then first-page response time is <= 1000 ms at P95. Given a 24-hour range with no filters, When querying, Then total count and first page load in <= 500 ms at P95. Given 50 concurrent users issuing representative queries, When measured over 15 minutes, Then error rate < 0.5% and UI remains responsive without timeouts. Given indexes exist on timestamp, workspace_id, link_id, actor_pseudonym, trust_tier, action_type, and outcome, When explain plans are executed on representative queries, Then index usage is confirmed and full scans of non-partitioned data are avoided.
Exportable Compliance Reports
"As a compliance manager, I want to export signed audit logs and summaries so that I can provide evidence for audits and client reviews."
Description

Enable export of selected audit events and summaries to CSV and JSON, plus a signed PDF report that aggregates key metrics (access counts by trust tier, action distribution, top links, anomalies). Support date and filter scoping, server-side generation for large datasets, and download links with expiration. Provide an API for automated exports and scheduled delivery to email or external storage. Normalize timestamps to a chosen time zone and include a data dictionary with field definitions in each export.

Acceptance Criteria
On-demand filtered export to CSV/JSON with time zone normalization
Given a user with Export Compliance Reports permission and Ghost Ledger contains audit events across multiple trust tiers and actions And the user selects a date range, any combination of trust tiers, action types, and optional link IDs And the user selects an output format of CSV or JSON and a time zone "America/New_York" When the user clicks Generate Export Then an export job is created with a unique job ID and initial status "queued" And when the job completes, the downloaded bundle contains a single data file (data.csv or data.json) and a data_dictionary file And the data file contains only events that match the selected filters within the date range And all timestamp fields in the data file are normalized to the selected time zone in ISO 8601 with offset (e.g., 2025-09-01T10:30:00-04:00) And the number of records in the data file equals the count of matching events in Ghost Ledger for the same filters and range And the data_dictionary defines every field present in the data file with name, type, description, and units (if applicable)
Digitally signed PDF compliance summary report with metrics and anomalies
Given a user with Export Compliance Reports permission selects a date range and filters and chooses output format Signed PDF When the user requests generation Then the generated PDF includes sections: Access counts by trust tier (table), Action distribution (counts and percentages), Top 10 links by access count, and an Anomalies summary that lists any detected anomaly categories with counts or states "No anomalies detected" if none And the PDF clearly displays the selected time zone and date range And the PDF includes an appendix Data Dictionary for reported fields and metrics And the PDF is digitally signed; the signature validates with the embedded certificate and shows "Document not modified" in a standards-compliant PDF reader And the PDF metadata includes generated_at (ISO 8601 in selected time zone), report_id, and schema_version
Server-side generation for large datasets with progress and expiring downloads
Given the estimated export size exceeds 50,000 events or 10 seconds of compute time When the export is requested Then generation runs server-side asynchronously and is not blocked by the client session And the job status is queryable via GET /exports/{job_id} returning statuses: queued, running, completed, failed, plus percent_complete And upon completion, the API returns a time-limited signed download URL And the download URL expires after 24 hours by default and is configurable per request between 1 hour and 7 days And attempting to use the URL after expiry returns HTTP 410 Gone and no file is served And partially completed or failed jobs never expose a download URL
Programmatic exports via API with filters and time zone
Given a client authenticated with an API token with scope exports:write When it POSTs to /api/exports with payload including format (csv|json|pdf), date_range, filters (trust_tiers[], action_types[], link_ids[]), and time_zone Then the API responds 202 Accepted with a job_id and status "queued" And the client can poll GET /api/exports/{job_id} until status "completed", then retrieve artifact URLs And the exported artifact(s) contain only filtered data, with timestamps normalized to the specified time zone, and include a data dictionary (for csv/json) or appendix (for pdf) And requests with an invalid time zone return 400 with a descriptive error And requests without the exports:write scope return 403 Forbidden
Scheduled delivery to email and external storage
Given a user creates a schedule specifying frequency (daily|weekly|monthly), run_time, time_zone, format (csv|json|pdf), date range rule (e.g., "previous day/week/month"), filters, and destinations (email recipients and/or S3 bucket + key prefix) When the schedule triggers at the configured time Then the system generates the export server-side and delivers: - Email: message sent to each recipient with an expiring download link (valid 7 days) and report metadata - S3: object uploaded to the specified bucket/prefix with a key containing report_id and date range And on success, the schedule run is logged with run_id, status "delivered", and artifact locations And on failure, the run is logged with status "failed" and an error message, and a notification email is sent to the owner; the system retries up to 3 times with exponential backoff
Input validation, scoping, and defaults
Given a request to generate an export from the UI or API When the date range is invalid (end < start) Then the request is rejected with HTTP 400 and an explicit error code and message When no time zone is provided Then UTC is used and noted in the export metadata When any filter value is unknown (e.g., unsupported trust tier or action type, or a link_id not belonging to the organization) Then the request is rejected with HTTP 400 and a list of invalid parameters And date range scoping is inclusive of start and exclusive of end (start <= timestamp < end) across all exports
Tamper-Evident Integrity Chain
"As an auditor, I want cryptographic assurances that logs have not been altered so that I can trust the audit evidence."
Description

Add cryptographic integrity to the ledger by hashing each event and linking it into a rolling hash chain with periodic checkpoints signed by a KMS-managed key. Store checkpoints separately and expose a verification endpoint that recomputes hashes over a range to detect insertion, deletion, or modification. Optionally write immutable copies to WORM-compatible storage. Surface integrity status in the admin console and flag gaps or corruption for investigation.

Acceptance Criteria
Event Hashing and Chain Linking on Ingestion
- For each new ledger event, the system computes a SHA-256 hash over a canonical payload composed of: previousHash, eventId, isoTimestamp (UTC, RFC 3339), linkId, actorId (or pseudonymous subject), trustTier, action, and metadataHash (SHA-256 of canonical JSON metadata with lexicographically sorted keys, UTF-8, no insignificant whitespace). - The resulting hash is stored as a 64-character lowercase hex string and is immutable after write. - previousHash of the new event equals the hash of the immediately preceding event in the same ledger stream; the first event after a checkpoint sets previousHash to the checkpoint hash; the genesis event uses a documented sentinel value (e.g., 64 zeros). - Ingestion rejects any event whose recomputed hash does not match stored hash and returns a 409 Conflict with an error code HASH_MISMATCH. - Concurrent ingests are serialized per ledger stream to ensure a single unbranched chain (no two events share the same predecessor within a stream). - Recomputing the hash from stored event fields deterministically reproduces the stored hash for 100% of test fixtures.
KMS-Signed Periodic Checkpoints
- The system emits a checkpoint per ledger stream at least every 1,000 events or every 5 minutes, whichever occurs first. - Each checkpoint includes: streamId, sequenceNumber, lastEventId, lastEventHash, createdAt (UTC), and keyId. - The checkpoint payload is signed using an AWS KMS asymmetric key (ECDSA_SHA_256). The signature verifies with the corresponding public key from KMS GetPublicKey. - Checkpoints are written to a storage location physically and logically separate from the primary event store, with write-only access for the application and deny-modify policies for all principals. - On emission, the system persists the checkpoint hash and signature and returns success only after verifying the signature locally. - During KMS key rotation, new checkpoints use the new keyId; verification accepts checkpoints signed by any active or historical key registered for the stream.
Verification Endpoint: Range Integrity Proof
- POST /api/v1/ledger/verify accepts JSON: {streamId, startEventId, endEventId, useCheckpoints:boolean, verifyWorm?:boolean}. - When the chain between startEventId and endEventId is intact, the endpoint returns 200 with {status:"ok", checkedCount, startHash, endHash, checkpointVerified:boolean, durationMs}. - When tampering or gaps are detected, returns 200 with {status:"corrupt"|"gap", firstBadEventId, reason:"INSERTION"|"DELETION"|"MODIFICATION"|"MISSING_CHECKPOINT", expectedHash?, actualHash?, position, checkedCount}. - Input validation: 422 for malformed input; 404 for unknown streamId or anchors; 400 if the range exceeds the configured maximum (e.g., >100,000 events) without useCheckpoints. - Performance: p95 verification time ≤ 3,000 ms for ranges ≤ 20,000 events when a relevant checkpoint is present; ≤ 8,000 ms without checkpoints. - If verifyWorm=true and WORM is enabled, the response includes wormVerified:boolean and fails with status:"gap" and reason:"WORM_MISSING" if any required immutable copy is absent.
Tampering Detection: Insertion, Deletion, Modification Cases
- Artificial insertion: an extra event inserted between two valid events causes the verification endpoint to return status:"corrupt" with reason:"INSERTION" and firstBadEventId equal to the inserted event. - Deletion: removal of an event in the verified range results in status:"gap" with reason:"DELETION" and firstBadEventId equal to the first event after the missing one; expectedHash reflects the previousHash mismatch. - Modification: any change to stored event fields used in hashing results in status:"corrupt" with reason:"MODIFICATION" and includes expectedHash and actualHash at the point of divergence. - Out-of-order write: two events referencing the same previousHash within a stream is detected and reported as status:"corrupt" with reason:"FORK". - Each case is covered by automated tests that seed the anomaly and assert the exact reason code and firstBadEventId.
Admin Console Integrity Status and Operator Workflow
- The admin console displays per-stream integrity status: Healthy, Gap, Corrupt, or Unknown, based on the most recent automated verification results. - The console shows lastVerifiedAt, rangeVerified (start..end), and the most recent checkpoint keyId. - Admins can trigger "Verify Now" for a selectable range; progress and result are shown inline within 10 seconds of completion. - When status is Gap or Corrupt, the console surfaces a banner with the reason code and a deep link to the firstBadEventId; an audit note can be attached by authorized users. - Alerts: on Gap or Corrupt, a notification is emitted via configured channels (email/webhook) within 5 minutes. - Access is restricted to users with the "Ledger:View" permission; triggering verification additionally requires "Ledger:Operate".
Optional WORM Immutable Copies
- When wormIntegrityCopies is enabled, each event and checkpoint is written to WORM storage using S3 Object Lock in Compliance mode with a configurable retention (default ≥ 365 days). - After write, the system verifies that the object has ObjectLockMode=COMPLIANCE and the retention date ≥ configured retention; failures are logged with error code WORM_NOT_LOCKED. - The immutable write is idempotent; retries do not create duplicate objects and are safe on transient failures. - Primary ingestion does not block on WORM write failure; the stream status reflects a degraded state "WORM Out of Sync" until backfilled successfully. - The verification endpoint with verifyWorm=true confirms presence of WORM copies for all events in range and includes wormVerified=true only if all are present and locked.
Continuity, Ordering, and Recovery
- Events within a ledger stream have a monotonically increasing sequenceNumber assigned at commit; no two events share the same sequenceNumber within a stream. - On service restart or failover, ingestion resumes using the last committed event and/or checkpoint without producing a fork; previousHash for the next event matches the lastEventHash or checkpoint hash. - Concurrent writers are coordinated (e.g., via transactional store/lock) to ensure single-writer semantics per stream; attempts to write out-of-order are rejected with 409 CONFLICT and code SEQUENCE_VIOLATION. - If a checkpoint write fails, event ingestion continues; the next successful checkpoint must still cover the missed range and verify correctly. - All integrity failures (hash mismatch, fork, missing checkpoint) are recorded with machine-readable codes and are queryable in operator logs.
Retention and Data Governance Controls
"As a privacy officer, I want configurable retention and anonymization so that we comply with regulations and minimize risk."
Description

Provide workspace-level controls for retention duration, automatic purge or anonymization, legal holds, and data residency. Support subject rights workflows by enabling lookup and targeted anonymization of pseudonymous actor IDs without breaking hash-chain verification for unaffected ranges. Record consent signals and policy versions applied to each event. Offer defaults that minimize data retention while meeting typical audit requirements, with auditability of all policy changes.

Acceptance Criteria
Workspace Retention Policy Configuration and Defaults
Given a new workspace is created with no changes to retention settings When the system initializes default data governance settings Then the default retention profile is applied with a retention duration of 90 days and action = anonymize And the default applies to all Ghost Ledger event classes unless explicitly overridden And the effective policy version is recorded and visible to Workspace Admins Given a Workspace Admin with Retention:Manage permission edits the retention settings When they set per-event-class retention duration between 1 and 1825 days and choose action in {purge, anonymize} Then invalid values (out of range, empty, non-integer) are rejected with inline validation errors And saving creates a new immutable policy version with editor identity, timestamp, and diff of changes And the new version is marked "Active" and scheduled to take effect within 1 hour Given a prior policy version exists When a Workspace Admin selects "Revert to this version" and confirms with reason Then the selected version becomes Active and the reversion is recorded in the audit log with previous/next version IDs
Automated Purge/Anonymization Job Execution and Reporting
Given an Active retention policy exists When the daily governance job runs Then events older than their policy threshold and not under legal hold are processed according to action And events with action = purge are permanently removed from primary and replica stores within 24 hours And events with action = anonymize have direct identifiers removed and actor IDs irreversibly transformed, preserving event timing and non-sensitive fields And hash-chain verification passes for all unaffected segments; segment root hashes for unaffected date ranges remain unchanged Given the job completes When an Admin views the job report for a date range Then the report shows counts by action (purged, anonymized, skipped-held), duration, failures, retry counts, and success rate And the failure rate is below 0.1% or alerts are raised to On-Call and surfaced in the Admin UI And each run has a unique Job ID with downloadable evidence (before/after counts and verification proof)
Legal Hold Creation, Scope, and Enforcement
Given a Workspace Admin creates a legal hold When they specify scope by actor ID(s), event type(s), and date range with a required reason and optional expiration date Then the hold is saved as Active, assigned a Hold ID, and recorded in the audit log with creator identity Given Active legal holds exist When the governance job evaluates events Then any event matching a hold scope is excluded from purge/anonymization and counted as skipped-held And attempts to manually purge or anonymize held events are blocked with an explanatory error citing the Hold ID Given an Active hold with expiration date passes its expiry When the governance job next runs Then the hold transitions to Expired and previously held events become eligible under the current policy And changes to hold status (activate, modify, release) require Retention:Manage permission and are fully audited
Data Residency Selection and Enforcement
Given a Workspace Admin sets data residency to one of {EU, US, APAC} When the setting is saved Then new and existing Ghost Ledger data (primary, replicas, and backups) are stored only in the selected region And exports and pre-signed URLs are served from endpoints in the selected region And cross-region replication is disabled for covered datasets Given data residency is configured When a residency compliance check runs Then storage location metadata for a sampled set of objects matches the configured region 100% of the time And any violation triggers a Critical alert and blocks further exports until resolved Given an Admin attempts to change residency while a region-incompatible legal hold exists When they confirm the change Then the change is blocked with a reason indicating conflicting hold scope and required remediation steps
Subject Rights Lookup and Targeted Anonymization by Pseudonymous Actor ID
Given a Workspace Admin initiates a subject lookup by pseudonymous actor ID When they submit the actor ID and date range Then the system returns all matching events with counts by event type within 60 seconds for up to 1 million events Given the Admin requests targeted anonymization for the returned actor ID When the request is confirmed with a recorded legal basis Then all matching events are updated to replace the actor ID with a non-linkable surrogate and remove associated indirect identifiers And a downloadable evidence package (request details, counts affected, timestamps) is generated And unaffected segments retain identical hash-chain roots before vs. after; affected segments produce new roots that verify And the operation completes within 7 days of request (target 24 hours)
Per-Event Consent Signal and Policy Version Recording
Given an event is written to the Ghost Ledger When the event is persisted Then the event includes consentSignal in {consented, legitimateInterest, contract, other}, consentSource, and appliedPolicyVersionId And these fields are non-null and validated against allowed values Given an auditor queries events for a time window When results are returned Then 100% of events include consentSignal, consentSource, and appliedPolicyVersionId And exports include these fields and their schemas in the header/manifest
Auditability and Tamper Evidence for Policy Changes
Given a user with Retention:Manage or Workspace:Owner changes retention settings, legal holds, or data residency When the change is saved Then an audit event is appended capturing who, when, what (before/after), reason, workspace ID, and correlation ID And the audit event is included in the append-only hash chain Given the audit verification endpoint is called for a date range When verification runs Then the endpoint returns Pass with current root hash if no tampering is detected, or Fail with the first divergent record ID if tampering is detected Given a Viewer role user attempts to change governance settings When they submit changes Then the action is denied with 403 Forbidden and no audit mutation is recorded beyond the denied access attempt

Respect Profiles

Let teammates opt into cultural and life-pattern preferences—observance windows, Fridays off, school runs—without exposing sensitive details on their calendars. Culture Clock honors these profiles in every suggestion and link, so you avoid unintentionally intruding. Admins can set org defaults by locale while individuals fine‑tune theirs, ensuring respectful scheduling that stays consent‑based and private.

Requirements

Private Preference Profile Schema
"As a teammate, I want to express my scheduling preferences without exposing personal details so that my availability is respected while my privacy is protected."
Description

Define a structured, privacy-first profile that captures cultural and life-pattern preferences (e.g., observance windows, Fridays off, school runs), working hours, locale, and holiday sources without exposing sensitive details. Provide granular visibility controls (private by default), data minimization, and encryption in transit/at rest. Include a self-service UI for users to opt in, edit, and fine-tune preferences, with clear previews of the impact on availability. Ensure the system exposes only availability outcomes to others, never the underlying reasons, and integrates seamlessly with existing account settings and onboarding flows.

Acceptance Criteria
Self-Service Opt-In and Edit with Availability Preview
- Given an authenticated user without a profile, when they open Respect Profiles, then opt-in is OFF by default and preference fields are hidden until opt-in is toggled ON. - Given opt-in is ON, when the user adds/edits/removes observance windows, Fridays off, or school-run windows using time ranges, then the availability preview updates within 250 ms and reflects the next 14 days. - Given valid non-overlapping time ranges, when Save is clicked, then changes persist and the same preview re-renders after page reload. - Given the user clicks Cancel or navigates away without saving, then no changes are persisted. - Given overlapping or invalid ranges (end <= start or > 24h), when Save is attempted, then inline errors are shown and Save is disabled.
Granular Visibility Controls Private by Default
- Given a newly created profile, then visibility for all fields is Private by default. - Given any visibility setting, when another user, admin, ICS export, or API requests availability, then only available/unavailable windows are returned and no underlying reasons or field values are included. - Given audit logs or telemetry, then no sensitive profile field values are logged; only event types and non-reversible IDs are recorded. - Given the profile owner views their profile, then they can see and edit raw field values; no other role can view them.
Data Minimization and Schema Constraints
- Given profile data is submitted, when it contains fields outside the approved schema (locale, working hours, holiday sources, observance windows, Fridays off, school-run windows, focus blocks), then the request is rejected with HTTP 400 and a schema error. - Given profile windows are stored, then they are saved as structured time ranges (start, end, day-of-week, timezone/offset reference) without free-text reasons. - Given locale is stored, then it is saved as a valid BCP 47 tag and holiday sources as provider IDs; no religion/ethnicity free-text is stored. - Given PII, then only userId and timezone are stored with the profile; no addresses, phone numbers, or notes are accepted. - Given data export, when a user exports their data, then only the schema-defined fields are included; any unknown or deprecated fields are excluded.
Encryption and Security Controls
- Given any network request carrying profile data, then TLS 1.2+ is enforced with HSTS and strong ciphers; non-TLS requests are rejected with 301/403. - Given profile data at rest (DB, search index, backups), then it is encrypted with AES-256 using KMS-managed keys; key rotation occurs at least every 90 days. - Given access to profile data, then service accounts are restricted by least-privilege IAM and all access is audited; direct ad-hoc database reads by humans are blocked. - Given secrets/keys, then they are never written to logs and are stored only in a managed secret vault. - Given a security test suite runs in CI, then it verifies TLS configuration, at-rest encryption flags, and denies plaintext connections.
Org Defaults and Individual Overrides (Consent-Based)
- Given an org admin sets locale-based default windows, then those defaults are offered as suggestions in the user’s editor but are not active until the user opts in and saves. - Given a user is opted in, when their personal settings conflict with org defaults, then the user’s settings take precedence in all scheduling outcomes. - Given an admin changes org defaults after users have opted in, then affected users are notified and changes are not applied until the user reviews and accepts. - Given a user has not opted in, then scheduling suggestions do not apply org defaults that would reduce availability.
Scheduling Engine Honors Profiles, Exposes Outcomes Only
- Given participants with active profiles, when generating meeting windows, then the engine excludes times outside their working hours, holiday sources, observance windows, focus blocks, and life-pattern windows. - Given a public scheduling link or API response, then only availability windows (start, end, time zone) are shown; no labels like “observance” or “school run” appear anywhere. - Given a query for a specific time slot, then the API returns only available: true/false; no reason codes or profile metadata are included. - Given a suggested time would fall after-hours or during an unavailable window, then that time is never presented as an option. - Given DST transitions across locales, then availability calculations remain correct and contiguous windows are not misaligned.
Onboarding and Account Settings Integration
- Given a new user completes onboarding, then an optional Respect Profiles step is shown with clear consent language; Skip leaves no profile data stored. - Given the user completes the step, then a pre-save summary and preview are shown and must be confirmed before persistence. - Given account settings, then the user can access, edit, export, and delete their profile; deletion removes primary records within 24 hours and schedules backup deletion within 30 days. - Given analytics, then only opt-in/opt-out events and feature usage counts are recorded; no profile field values are collected.
Scheduling Engine Constraint Enforcement
"As a meeting organizer, I want suggestions to automatically exclude teammates’ protected windows so that I don’t accidentally schedule intrusively."
Description

Extend the scheduling engine to interpret Respect Profiles as hard and soft constraints, honoring protected windows, work hours, and holidays across time zones. Ensure suggestions and auto-scheduling exclude protected time unless an explicit, approved override exists. Support multi-participant aggregation, constraint precedence, and tie-breaking rules, with performance optimizations for large participant sets. Provide deterministic, reproducible outputs and robust handling of edge cases (e.g., no feasible windows), returning clear, privacy-preserving guidance for organizers.

Acceptance Criteria
Hard Protected Windows Are Excluded From Suggestions
Given participants with Respect Profiles defining hard protected windows, work hours, and holidays across different time zones When the organizer requests suggested meeting windows for a specified duration and date range Then the engine returns only slots that do not overlap any participant's hard protected windows, are within each participant's work hours, and exclude their observed holidays And no returned slot intersects any participant’s hard protected window after time zone conversion And each returned slot indicates zero hard-conflict violations without revealing participant identities or reasons
Soft Constraints Influence Ranking But Not Feasibility
Given participants with soft constraints (e.g., preferences) in their Respect Profiles When suggestions are generated and at least one feasible slot has zero soft-penalty Then all zero-penalty slots are listed before any slot with soft penalties in deterministic order And auto-scheduling selects a zero-penalty slot when available When no zero-penalty slots exist Then the engine may return soft-penalty slots flagged with a nonzero softPenalty score but does not auto-schedule unless allowSoftOverride=true And soft-penalty metadata is aggregate and non-identifying
Explicit, Approved Overrides Required To Book Into Protected Time
Given a candidate slot that overlaps any participant’s hard protected time When the organizer attempts to auto-schedule that slot Then booking is rejected unless a valid, unexpired override approval exists for each affected participant covering that slot and duration And override approvals are explicit, time-bounded, revocable, and auditable without exposing profile details to other participants And if rejected, the response message states "Slot falls within protected time" without disclosing the type of protection And suggestions never include hard-conflict slots unless includeOverrides=true and all required approvals are present
Aggregation Applies Constraint Precedence Correctly
Rule: Constraint precedence is Hard Protected Windows > Holidays (org/legal) > Individual Work Hours > Org Default Work Hours > Soft Constraints Given participants whose individual work hours conflict with org defaults When generating suggestions Then individual work hours override org defaults for that participant And any overlap with a higher-precedence hard constraint eliminates the slot regardless of lower-precedence satisfaction And holidays are treated as hard constraints unless the participant explicitly opts into working that holiday in their profile And the final feasible set equals the intersection across all participants after applying precedence
Deterministic Suggestion Ordering And Tie-Breaking
Given identical inputs (participants, date range, duration, constraints, search granularity, algorithm version) When suggestions are generated multiple times Then the set and order of suggestions are identical across runs And ties on score are broken by ascending UTC start time, then lower transition penalty, then lexicographic slotId And the response includes a resultSignature that stably hashes inputs and algorithm version to support reproducibility checks
Graceful No-Feasible-Windows Response
Given constraints that yield no feasible intersection across participants When generating suggestions Then the engine returns HTTP 200 with an empty suggestions array And includes a guidance object with aggregate, non-identifying counts per blocking constraint type (e.g., hardProtected, holiday, workHours, timeZoneSpread) And includes nextBestActions (e.g., expand date range, reduce duration, allow soft-penalty slots, request overrides) and a deterministic explanationId And no sensitive or participant-identifying details from Respect Profiles are returned
Performance And Scalability For Large Participant Sets
Given a request with 200 participants across ≥8 time zones and a 14-day search window for 60-minute meetings at 15-minute granularity When generating suggestions on standard production hardware Then p95 latency ≤ 2000 ms and p99 latency ≤ 4000 ms, with peak memory ≤ 300 MB for the request And for result sets >1000 candidate slots, the API streams or paginates results, returning the first page within 800 ms And performance targets hold with Respect Profiles enabled and privacy-preserving aggregation active
Locale-Based Org Defaults
"As an admin, I want to set respectful defaults by region so that new teammates are protected without manual setup."
Description

Enable admins to define organization-level default Respect Profiles by locale/region, including standard work hours, common observance patterns, and holiday calendars. Apply defaults automatically to new users based on location or group membership, with opt-in prompts and user-level fine-tuning. Provide policy versioning, change previews, and impact analysis before rollout. Support importing trusted holiday sources and mapping employees to multiple locales when needed, while keeping individual settings private from peers.

Acceptance Criteria
Create Locale Default Respect Profile
Given I am an org admin with Policy:Manage permission When I create a default Respect Profile for locale "US-Pacific" with valid work hours, observance patterns, and a holiday source Then the policy is saved as Draft version v1.0 and listed in the Policy Catalog Given any required field is missing or invalid When I attempt to save the policy Then the save is blocked and field-level validation errors specify the exact issues Given a valid Draft exists When I click Publish Now Then the policy status changes to Active with an activation timestamp and the version remains v1.0
Auto-Apply Defaults to New Users by Locale/Group
Given a new user has a resolvable locale "US-Pacific" or belongs to a group mapped to that locale When the account is provisioned Then the Active "US-Pacific" policy is assigned in Pending Opt-In state and an opt-in prompt is queued for first session Given a new user has no resolvable locale or matching group When the account is provisioned Then no policy is assigned and the user is flagged "Locale unresolved" in the admin dashboard Given multiple mappings could match a user When provisioning runs Then a deterministic precedence is applied and the chosen match reason is recorded in the audit log
Opt-In Prompt and User Fine-Tuning
Given a user has a Pending Opt-In locale policy assignment When they sign in Then an opt-in modal appears within 3 seconds showing a summary of proposed defaults and actions: "Accept & Fine‑Tune" and "Decline" Given the user clicks Accept & Fine‑Tune When they adjust personal settings and save Then changes are stored in the user's private profile within 1 minute and org policy remains unchanged Given the user clicks Decline When the modal is dismissed Then no locale defaults are applied and the user remains Unconfigured with a non-blocking reminder available in Settings Given a user has accepted When a peer views their scheduling link Then only resultant availability is displayed without labels or reasons for blocks
Policy Versioning, Preview, and Impact Analysis
Given an Active policy exists for a locale When an admin edits and saves changes Then a new Draft version is created with a change summary and field-level diff Given a Draft version exists When the admin clicks Preview Impact Then the system shows counts of affected users by locale/group and predicted change in available meeting windows for the next 4 weeks within 10 seconds for orgs up to 5,000 users Given the admin schedules a rollout with an effective date/time When the effective time arrives Then the new version becomes Active, the previous version is Archived, and users with Pending or Accepted states receive a notice; user personal overrides remain intact Given the rollout is problematic When the admin clicks Rollback to previous version Then the prior Active version is reinstated and a rollback audit entry is recorded; personal overrides are not changed
Import Trusted Holiday Sources
Given an admin provides an ICS URL or connects a trusted holiday provider When Validate is clicked Then the system fetches events, validates schema, de-duplicates overlapping holidays, and shows a preview or a descriptive error Given the admin maps the validated source to one or more locales When Save is clicked Then the mapping is persisted and policies referencing those locales use the imported holidays Given the upstream source changes When the nightly sync runs Then new holidays are added, removed ones are retired, and impacted policies are flagged with a changelog for review
Map Users to Multiple Locales
Given a user is assigned to multiple locales (e.g., US-Pacific and Japan) When availability is computed Then non-working times and observances are treated as the union of blocks and working windows as the intersection, yielding the most restrictive availability Given holidays overlap under different names When merged Then a single block is applied without duplication Given an admin removes one of the user's locales When recomputation runs Then the user's availability updates within 1 minute to reflect the remaining locale(s)
Privacy and Admin Visibility Controls
Given a non-admin peer views another user's calendar or scheduling link When the user has locale defaults or personal overrides Then the peer sees only free/busy availability and cannot view locale, holiday source, observance labels, or personal notes Given an admin opens Impact Analysis When viewing affected users Then aggregated counts and non-identifying statistics are shown by default; viewing named users requires "User Privacy:View Identifiable Impact" permission and is audited Given any policy assignment or change occurs When audit logs are queried Then entries include who, what (policy/version), when, and match reason without exposing sensitive personal details
Profile-Aware Smart Links
"As a host, I want my scheduling links to respect my protected times so that external guests can only book appropriate slots."
Description

Update Timeglue’s scheduling links to enforce the owner’s Respect Profile end-to-end. Show invitees only the time slots that comply with protected windows, work hours, and holidays, adjusted to each viewer’s time zone. Do not disclose reasons for unavailability; present only permissible slots. Support multi-host links, expiration and tokenization, and fallback flows when no slots are available (e.g., request an exception). Ensure seamless compatibility with existing link-sharing and calendar invite flows.

Acceptance Criteria
Owner Profile Enforcement in Smart Link Slots
Given an owner has a Respect Profile with protected windows, work hours, and holidays configured And an active smart link referencing that owner exists And an invitee in a different time zone opens the link When available slots are computed and rendered for the next configured horizon Then every displayed slot starts and ends outside protected windows And every displayed slot falls within the owner’s work hours And no displayed slot occurs on an owner-observed holiday And all slots are shown in the invitee’s detected local time And the UI and API responses do not disclose reasons for unavailable times
Invitee Time Zone Rendering and DST
Given an invitee opens a smart link within two weeks of a DST change in either the owner’s or invitee’s locale When the system auto-detects the invitee’s time zone Then each displayed slot reflects the correct time conversion including DST offsets And switching the displayed time zone manually recalculates all slots accurately within 1 second And the booked confirmation reflects the invitee’s chosen time zone and includes the canonical IANA zone identifier
Multi-Host Smart Link Intersection
Given a multi-host smart link with two or more owners, each with a Respect Profile When generating availability for the link Then only time slots that are permissible for all owners (intersection) are displayed And capacity is respected such that double-booked owners are excluded from slots And if no intersection exists for the visible range, a no-availability state is shown with the Request Exception action And the UI and API do not reveal which host(s) caused conflicts or why
Link Tokenization and Expiration
Given a smart link is created with a unique opaque token and an expiration timestamp When the link is accessed with a valid token before expiration Then permissible slots render successfully When the link is accessed without a token or with an invalid token Then no slot data is returned and a generic unauthorized message is shown (HTTP 401/403) When the link is accessed after expiration Then an expired-state screen is shown with an option to request a new link or contact the owner, and no slot data is exposed And tokens are at least 128 bits of entropy and are scoped to a single link
No-Availability Fallback: Request Exception
Given a smart link displays no available slots for the next configured horizon When the invitee selects Request Exception Then the invitee can submit 1–3 preferred time windows in their local time And the owner receives an in-app notification and email containing the requested windows And an audit log entry is created linking the request to the smart link and token And the confirmation screen shows that the request was sent without revealing any profile constraints or reasons
Calendar Invite Flow Compatibility
Given an invitee books a time via a smart link When the booking is confirmed Then calendar events are created on all hosts’ calendars and an ICS invite is sent to the invitee And the event details include only title, time, attendees, and meeting link, with no references to protected windows or holidays And rescheduling via the link updates the original event instance for all parties And cancellation via the link removes the event for all parties and sends notifications
Admin Defaults and Individual Overrides Applied
Given an organization has locale-based Respect Profile defaults and an owner has partial overrides When an invitee opens the owner’s smart link Then slot computation uses the owner’s overrides where defined and org defaults otherwise And changing the owner’s profile takes effect for existing links within 5 minutes And no details about org defaults or owner profile settings are exposed to invitees in UI or API
Consent-Based Overrides
"As a teammate, I want to approve one-time exceptions when necessary so that critical meetings can happen without eroding my boundaries."
Description

Provide a structured exception workflow for cases with limited or no feasible windows. Allow organizers to request a one-time override specifying candidate times; notify affected users to approve, deny, or propose alternatives without revealing sensitive details. Apply overrides narrowly (time-bound/single event), log consent, and reflect the override in the scheduling engine. Include clear UX nudges, reminders, and audit trails for compliance while keeping privacy protections intact.

Acceptance Criteria
Initiate One-Time Override When No Feasible Windows Exist
Given an organizer has selected attendees and the system finds 0 feasible windows in the next 14 days or fewer than 2 windows overall, When the organizer opens the override dialog, Then the UI displays a consent-based exception guardrail and links to any available windows. Given the override dialog is open, When the organizer completes the form, Then they must select 1–5 candidate times (date + start/end, timezone-normalized) and provide a reason of at least 10 characters. Given the organizer is selecting candidates, When any duplicate or invalid time is chosen, Then the form blocks submission with a clear error and highlights the offending inputs. Given the organizer attempts to apply the override to a series, When submitting, Then the system blocks and requires selecting a single occurrence only. Given the organizer submits the request, When it is accepted by the system, Then a unique request_id is created, affected attendees are enumerated, the request is logged, and a confirmation is shown within 2 seconds.
Recipient Consent Flow: Approve, Deny, or Propose Alternatives Privately
Given a recipient receives an override request, When they open it, Then the view shows event title, organizer, candidate times in local time, and a generic conflict label (e.g., "outside preferred hours") without exposing profile details. Given the recipient is responding, When choosing an action, Then they can Approve exactly one candidate, Deny all candidates, or Propose up to 3 alternative times that comply with their profile without entering free text. Given the recipient submits a response, When it is recorded, Then the system stores timestamp (UTC) and IP, updates the request status, and shows a confirmation within 2 seconds. Given no action is taken, When 48 hours elapse from request creation, Then the request expires for that recipient and is treated as no consent. Given organizer views responses, When checking status, Then only per-recipient statuses and proposed times are visible; no profile attributes or sensitive reasons are displayed.
Narrow Scope and Expiry of Consent
Given a recipient approves candidate time T for request R, When the organizer schedules the event, Then the consent applies only to event E at time T and does not modify the recipient’s profile or defaults. Given consent exists for T, When the organizer attempts to reschedule to any other time or date, Then new consent is required from all affected recipients. Given consent was granted, When the event is not scheduled within 7 days of approval, Then the consent automatically expires and the candidate is no longer schedulable via override. Given an override was used for a single occurrence, When the organizer attempts to apply it to a series or another event, Then the system blocks the action and prompts for a new request. Given an approved override, When the organizer cancels the request or event before scheduling, Then the consent remains in the audit trail but cannot be reused.
Scheduling Engine Applies Consented Overrides
Given at least one candidate time has approvals from all required attendees, When the organizer schedules via Timeglue or a smart link, Then the engine allows that time despite conflicts with respect profiles for that event only. Given multiple candidates are present, When some lack unanimous required approvals, Then those candidates are not schedulable and are visually disabled in the UI. Given an event is scheduled using an override, When invites are generated, Then a non-visible override flag is attached for auditing and no override reason appears in attendee-visible fields. Given approved candidates exist, When showing suggestions to the organizer, Then approved candidates are surfaced first and non-approved candidates are suppressed from auto-suggestions. Given a required attendee withdraws approval before scheduling, When the organizer attempts to schedule, Then that candidate is blocked until fresh consent is obtained.
Override Notifications, Reminders, and Expiration
Given an override request is submitted, When notifications are sent, Then affected recipients receive in-app and email notifications within 1 minute containing event title, organizer, candidate times, and expiration timestamp, without profile details. Given a recipient has not responded, When 24 hours remain until expiration, Then the system sends a single reminder via the same channels. Given a recipient responded, When they change their decision before scheduling or expiration, Then the latest response supersedes prior ones and all changes are versioned in the audit trail. Given all required approvals are collected or the request expires, When status changes, Then the organizer is notified within 1 minute and the request is marked Complete or Expired respectively. Given the organizer cancels the request, When cancellation occurs, Then all recipients are notified and any pending links are invalidated immediately.
Comprehensive Audit Trail and Export
Given any override request lifecycle event occurs (requested, approved, denied, proposed, canceled, expired, scheduled), When logging, Then an immutable record is written with request_id, event_id, organizer_id, attendee_id (if applicable), action, UTC timestamp, IP, user-agent, candidate times (redacted to IDs if configured), and outcome. Given audit records exist, When an org admin with Audit permission filters by date range, organizer, attendee, or event, Then results return within 3 seconds for up to 10k records. Given an admin requests export, When exporting filtered results, Then a CSV is produced within 60 seconds for up to 100k records and contains no sensitive profile attributes. Given the audit store is queried or exported, When checksums are validated, Then tamper evidence verifies append-only integrity; mismatches trigger an admin alert. Given API access to audit data, When a token without Audit permission requests records, Then access is denied with 403 and no data leakage occurs.
Privacy Non-Disclosure in Organizer and API Views
Given the organizer views an override request or responses, When inspecting details, Then no sensitive profile attributes (e.g., observance names, personal routines) are displayed; only generic conflict categories are shown. Given recipients respond, When using the UI, Then no free-text fields are available; alternative proposals are made via time selection only, preventing inadvertent disclosure. Given system-generated emails, ICS files, calendar descriptions, and API responses, When they reference the override, Then they omit override reasons and profile specifics and include only neutral phrasing. Given public smart links are shared, When recipients access them, Then link metadata and page content avoid revealing or inferring protected profile details beyond generic availability messaging. Given a privacy regression test suite runs, When scanning UI, API, logs, emails, and exports, Then zero instances of sensitive attribute leakage are detected before release (Pass gate).
Non-Disclosure Conflict Explanations
"As an organizer, I want clear yet non-revealing conflict reasons so that I can reschedule respectfully without prying."
Description

Introduce privacy-preserving conflict indicators in suggestions and calendar overlays that explain why a time is unavailable without revealing specifics (e.g., “Unavailable—protected window”). Indicate constraint severity (hard vs. soft) and provide actionable guidance (try earlier/later) while maintaining confidentiality. Ensure accessible visual design and consistent messaging across web, extensions, and API responses, with redacted reason codes for external consumers.

Acceptance Criteria
Suggestion List: Privacy-Preserving Conflict Indicator
Given a proposed time slot that violates any protected window from Respect Profiles or org defaults When the slot is rendered in the web app suggestions list Then the slot displays the exact message "Unavailable—protected window" and no additional rationale text And the message is taken from the approved set ["Unavailable—protected window"] only And no participant names, event titles, keywords, or profile attributes appear in the UI, tooltip, DOM, or accessibility tree for that slot
Calendar Overlay: Redacted Conflict Tooltip
Given the calendar day or week view with conflict overlays enabled When a time cell overlaps a protected window Then a redacted badge shows the exact text "Unavailable—protected window" with an info icon And opening the tooltip reveals only the same message plus the severity label, without any personal or event details And the tooltip and badge content are identical between mouse, touch, and keyboard interactions
Severity Indication and Behavior
Given a conflicting slot with severity "hard" When rendered on any surface (web, extension, overlay) Then a visible "Hard conflict" label and stop icon are shown, and the primary booking action is disabled And the contrast ratio of text to background is >= 4.5:1 Given a conflicting slot with severity "soft" When rendered on any surface Then a visible "Soft conflict" label and warning icon are shown, and the booking action remains enabled with a non-blocking warning Given overlapping constraints of mixed severities When computing the slot's final severity Then "hard" takes precedence over "soft" and only one severity label is displayed
Actionable Guidance Chips
Given a conflicted slot in the suggestions list When it is rendered Then guidance chips labeled "Try earlier" and "Try later" are displayed and focusable Given the user activates "Try earlier" When earlier available slots exist within the scheduling horizon Then at least the nearest 3 earlier available slots are surfaced, sorted by proximity Else the chip shows "No earlier times" and is disabled Given the user activates "Try later" When later available slots exist within the scheduling horizon Then at least the nearest 3 later available slots are surfaced, sorted by proximity Else the chip shows "No later times" and is disabled And no guidance UI reveals the underlying protected reason
Cross-Platform Messaging Consistency
Given the same account, calendar set, and time slot When the slot is viewed in the web app suggestions, the browser extension overlay, and via the Availability API Then the message text is exactly "Unavailable—protected window" on all surfaces And the severity value is identical across surfaces and is one of ["hard","soft"] And the available guidance set is identical across surfaces, with the same enabled/disabled state And the message_key "conflict.protected_window" is used consistently in API and client telemetry
External API Redaction
Given an external consumer calls GET /v1/availability using a public link token When a time slot is suppressed by a protected window Then the JSON response for that slot includes only: availability="unavailable", reason_code="PROTECTED_WINDOW", severity in ["hard","soft"], guidance array containing zero or more of ["TRY_EARLIER","TRY_LATER"], message_key="conflict.protected_window" And the response omits any personal or event details, including but not limited to: event titles, profile types, free-text notes, calendar IDs, user IDs, locations, or keywords Given an authenticated internal consumer with full org scope calls the same endpoint When a time slot is suppressed by a protected window Then the response remains redacted as above and does not include any additional personal or event details
Accessibility Compliance for Conflict Indicators
Given any conflict indicator or guidance chip in the web app or extension When evaluated for accessibility Then text contrast ratio is >= 4.5:1, essential icons contrast >= 3:1, and color is not the sole means of conveying severity (text+icon present) Given keyboard-only navigation When focusing conflict indicators and guidance chips Then each element is reachable in a logical order, has a visible focus ring, and is operable with Enter/Space Given a screen reader user When encountering a conflicted slot Then the announcement includes "Unavailable—protected window", the severity label ("Hard conflict" or "Soft conflict"), and the availability of "Try earlier" and/or "Try later"
API & Integrations for Respect Profiles
"As an IT admin, I want to synchronize profile defaults from our HR systems so that Timeglue stays aligned without manual effort."
Description

Expose secure APIs to manage Respect Profiles and defaults with fine-grained scopes and audit logging. Support SCIM/HRIS sync for locale, work hours, and holiday sources; allow ICS/CalDAV imports for holidays; and emit webhooks on profile changes. Provide OAuth-based access control, rate limiting, and enterprise configuration options. Deliver comprehensive documentation and mapping guides to keep Timeglue aligned with source systems while minimizing data exposure.

Acceptance Criteria
OAuth Scopes and Access Control for Respect Profiles API
Given a registered OAuth client with scope=respect_profiles.read When it GETs /v1/respect-profiles/{id} Then the response is 200 and includes only non-sensitive fields (e.g., locale, working_hours, holiday_source) Given a token lacking respect_profiles.write When it POSTs or PATCHes any /v1/respect-profiles endpoint Then the response is 403 with error=insufficient_scope and a WWW-Authenticate header listing required scopes Given a token with admin:org_defaults.write When it PUTs /v1/respect-profiles/defaults/{locale} Then the defaults update succeeds with 200 and subsequent GET returns the new values Given an invalid or expired token When any protected endpoint is called Then the response is 401 with error=invalid_token and no sensitive data is leaked in the body Given least-privilege enforcement When a client calls an endpoint outside granted scopes Then responses disclose no resource existence beyond standardized error metadata
Audit Logging for Profile Management with Redaction
Given a successful create/update/delete on a Respect Profile or org default When the operation completes Then an audit record is stored within 2 seconds including actor_id, action, target_id, timestamp (UTC), request_id, and source_ip Given sensitive fields (e.g., notes on observance) When audit entries are written Then values are redacted (e.g., ******) and only field names and change metadata are stored Given a write is denied (authz/validation) When the request is rejected Then an audit record is stored with outcome=denied and no raw payload values are persisted Given an auditor with scope=audit.read When they GET /v1/audit-logs?actor_id=&from=&to= Then 200 is returned with paginated, immutable records including a hash for integrity verification Given a user is hard-deleted under a compliance request When deletion finalizes Then audit entries retain minimal non-PII per retention policy and an audit redaction event is logged
SCIM/HRIS Sync for Locale, Work Hours, and Holiday Source
Given a SCIM 2.0 HRIS is configured with base_url and bearer token When Timeglue performs GET /Users and/or receives SCIM PATCH operations Then mapped attributes (locale, workingHours, holidaySource) update the user’s Respect Profile without storing unrelated PII Given a SCIM PATCH replace op on workingHours When the payload is valid Then the profile updates within 5 minutes and a profile.updated webhook is emitted Given concurrent changes from SCIM and manual API write When update timestamps conflict Then the latest updatedAt wins, with source precedence SCIM > manual in case of tie, and the losing write is 409 with resolution guidance Given a SCIM delete (deprovision) When received for a user Then the profile is soft-disabled within 60 seconds and the user is excluded from scheduling windows Given attribute mapping is invalid When sync runs Then a 422 error is returned for the affected user with precise path references and no partial application of changes
ICS and CalDAV Holiday Import
Given a valid ICS URL or CalDAV account is configured for a locale or user When synchronization runs Then non-working days are imported as blocks type=holiday with timezone-aware start/end boundaries Given duplicate holiday events across multiple sources When imports execute Then deduplication by UID (and date fallback) ensures a single block per holiday Given the ICS/CalDAV source is temporarily unreachable When sync attempts fail Then retries use exponential backoff and GET /v1/integrations/holidays/status reports last_success_at and error state Given holiday events include titles/notes When storing imported data Then only category and date metadata are persisted; titles/notes are dropped to preserve privacy Given daylight saving transitions When holidays span boundary days Then imported blocks align to local calendar days as observed in the locale
Webhooks on Respect Profile and Default Changes
Given a create/update/delete of a Respect Profile or a change to org defaults When the operation commits Then a webhook is enqueued and delivered within 30 seconds with type in {profile.created, profile.updated, profile.deleted, defaults.updated} Given webhook delivery to a subscriber endpoint When the request is sent Then payload contains event_id, type, occurred_at (UTC), profile_id or locale, changed_fields, and is signed with HMAC-SHA256 in X-Timeglue-Signature using the tenant secret Given the subscriber returns non-2xx or times out When delivery fails Then retries occur up to 8 attempts with exponential backoff and jitter; final failure is recorded and visible in GET /v1/webhooks/deliveries Given network instability causes duplicate deliveries When the same event is retried Then an Idempotency-Key header is included so consumers can safely de-duplicate Given a tenant rotates webhook secrets When both old and new secrets are configured during a rotation window Then signatures using either secret validate for 24 hours
API Rate Limiting and Quotas
Given a client exceeds its allocated rate limit (e.g., 600 requests/min read, 120 requests/min write) When additional requests are made within the window Then the API returns 429 with Retry-After and X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers reflecting the correct buckets Given distinct read and write buckets When a client performs mixed traffic Then reads and writes are counted separately per token and enforced independently Given an enterprise tenant has custom limits When configured by an admin Then limits take effect within 5 minutes and are accurately reflected in rate-limit headers Given bursts and clock skew When multiple concurrent requests occur Then the token-bucket/sliding-window algorithm enforces fairness without starvation and logs limit events for audit
OpenAPI Documentation and Mapping Guides
Given developers access the API documentation When they GET /openapi.json Then a valid OpenAPI 3.1 document is returned that validates against a schema linter and matches deployed endpoints and models Given endpoint documentation pages When viewed Then each endpoint includes curl, JavaScript, and Python examples that execute successfully against a public sandbox with provided test credentials Given SCIM/HRIS integrations When reading the mapping guide Then field mappings for locale, workingHours, and holidaySource are documented with example payloads, privacy considerations, and end-to-end sample transformations that pass provided contract tests Given API version changes When an endpoint is deprecated Then docs display a deprecation notice with a sunset date ≥ 90 days in the future and prior versions remain accessible via versioned URLs

Observance Aware

Auto-detects daily and weekly observance windows (e.g., prayer times, sundown eves, fasting breaks) by locale and season and quietly steers suggestions away from them. Proposes nearby alternates that keep agendas intact, with optional micro‑buffers before/after for travel or preparation. Keeps recurring meetings aligned as daylight changes, so a courteous plan stays courteous.

Requirements

Global Observance Data Engine
"As a scheduling engine, I want accurate, locale- and season-aware observance windows so that I can reliably constrain suggested times without manual inputs."
Description

Build a rules-driven engine that computes daily and weekly observance windows by locale and season, supporting both fixed weekly windows (e.g., sundown-to-sunset periods) and variable daily windows calculated from astronomical events (e.g., sunrise/sunset-based prayer times). Ingest and reconcile authoritative data sources and calculation methods per locale, honor time zones and DST, and expose normalized windows via an internal API with caching, versioning, and automated refresh. Provide locale inference from user/org settings with secure overrides, and degrade gracefully with safe defaults if data is unavailable. Output integrates with Timeglue’s constraint solver so observances are treated as schedule constraints alongside work hours, holidays, and focus blocks.

Acceptance Criteria
Locale Inference and Secure Override
Given a user has no explicit observance locale When the engine resolves locale Then it infers from organization defaults and the user’s IANA timezone and records the resolution path in metadata Given a user with permission attempts to set a locale override at user scope When the override is saved Then subsequent computations use the override, the change is audit‑logged with actor, scope, and timestamp, and removing the override reverts to inference Given a user without permission attempts to set an override When the request is made Then the API returns 403 and no override is stored Given conflicting organization‑ and user‑level overrides exist When computing windows Then the user‑level override takes precedence if present Given override data is stored When persisted at rest Then only the locale key and scope are stored (no PII), and storage is encrypted at rest and in transit
Fixed Weekly Observance Window Computation
Given a locale with fixed weekly sundown‑to‑sunset observance rules When computing windows for the week of a supplied date and IANA timezone Then the engine returns non‑overlapping [start, end) intervals in local civil time for each occurrence, tagged with type and ruleId Given a DST transition occurs during an observance window When computing interval boundaries Then boundaries reflect astronomical times in local civil clock and no unintended gaps or overlaps are introduced Given a weekly rule that crosses midnight When computing Then the interval spans days as a single window with correct calendar dates and normalized timestamps
Astronomical Observance Calculation with TZ and DST
Given coordinates, date, IANA timezone, and a locale’s configured calculation method When computing daily windows derived from sunrise/sunset Then start and end times deviate by no more than 60 seconds from the reference implementation for that method and are returned as RFC3339 timestamps with timezone offset, including calculationMethodId in metadata Given a day with a DST change When computing a 7‑day sequence of daily windows Then interval start times are strictly increasing by day with no duplicated or skipped windows due to clock shift Given a polar location with no sunrise or sunset on a date When computing Then the configured fallback (e.g., nearest‑day or angle‑based) is applied and the window is flagged approximate=true with the fallback strategy noted
Data Source Ingestion, Reconciliation, and Versioning
Given multiple authoritative sources are configured for a locale When nightly ingestion runs Then the engine fetches, validates schema and checksums, and reconciles differences using the locale’s precedence rules Given two sources differ by more than 5 minutes for the same window When reconciling Then the chosen value follows the configured strategy, the decision is recorded with a confidence score, and the ruleset version is incremented Given a client requests windows When responding Then the payload includes rulesetVersion (semver), calculationMethodId, and sourceIds, and clients may pin to a specific version; default uses latest stable Given a source fetch fails When ingestion completes Then the last known good dataset is retained, corrupt or partial data is not promoted, retries follow exponential backoff, and metrics/alerts are emitted
Normalized API Output with Caching
Given an API request specifying locale, IANA timezone, optional coordinates, and a date range up to 31 days When processed Then the response returns a chronologically sorted list of windows with fields: start, end, type, label, bufferBefore, bufferAfter, calculationMethodId, rulesetVersion, sourceIds, confidence, and flags Given repeated identical requests within cache TTL When served Then p95 latency is ≤ 200 ms and ETag support enables 304 responses; for cold cache, p95 latency is ≤ 800 ms Given invalid parameters (e.g., bad timezone, malformed date range) When requested Then the API returns 400 with machine‑readable error codes; unknown locale yields 404; rate limits return 429 with Retry‑After; all error responses are RFC7807 compliant Given more than 500 windows match the query When returning results Then results are paginated with deterministic cursor tokens and stable ordering by start time
Automated Refresh and Safe Default Degradation
Given the refresh scheduler runs When executing Then the engine precomputes and caches observance windows for the next 90 days per active locale and invalidates caches within 60 seconds of ruleset or method change Given all data sources are unavailable for a locale When a client requests windows Then the engine returns either a configured policy‑based fallback set or an empty array marked degraded=true with a warning code, and no stale data older than 7 days is served Given degradation occurs When monitoring evaluates Then an alert is emitted within 5 minutes and a public status endpoint reports affected locales and degradation start time
Constraint Solver Integration: Observance as Schedule Constraints
Given a set of participants with observance windows from the engine When the constraint solver computes candidate meeting times for a date range Then no suggested slot overlaps any observance window for any participant Given all candidate times conflict with observance windows When solving Then the solver requests nearest alternates and receives windows that enable proposing the closest non‑conflicting times within ±60 minutes unless overridden by configuration Given micro‑buffers are configured for an observance type When computing Then the solver treats buffers as part of the blocked interval by expanding start and end accordingly
User & Team Observance Preferences
"As a team lead, I want to set observance preferences for myself and my team so that scheduling respects our practices while remaining flexible when needed."
Description

Provide per-user and team-level settings to declare observance participation, strictness (hard block vs. soft preference), preferred calculation method where applicable, and default micro-buffer behavior. Allow discreet opt-in detection based on working location with explicit confirmation, and enable overrides for travelers or multi-location teams. Expose policy controls for admins (e.g., default strictness, allowed overrides) and merge preferences with organizational working hours and focus blocks to produce a unified availability profile per participant.

Acceptance Criteria
Per-User Observance Participation & Strictness
Given a user enables participation in an observance and sets Strictness = Hard Block When scheduling suggestions are generated Then any slot overlapping the computed observance windows for that user is excluded from available times Given a user sets Strictness = Soft Preference When scheduling suggestions are generated Then overlapping slots are shown demoted and require an explicit "override soft preference" confirmation to book Given a user disables participation in an observance When scheduling suggestions are generated Then observance windows are not considered for that user
Team Admin Policy Defaults & Allowed Overrides
Given an org admin sets default Strictness = Hard Block and disallows user overrides When a new user is created Then the user inherits Hard Block and the UI prevents loosening strictness, showing a policy message Given a member attempts to change strictness against policy When they save preferences Then the change is rejected with a policy error and no update is persisted Given admin allows overrides When a user changes strictness Then the change is saved and an audit log records user, timestamp, and old/new values
Discreet Location-Based Detection & Consent
Given the system detects a likely observance based on locale/season When the user first opens Observance Preferences Then a consent prompt appears with options "Review & Confirm" and "Decline", and no settings are auto-applied Given the user selects "Decline" When preferences are saved Then no detection-based settings are applied and the user is not prompted again for 90 days unless they revisit Preferences Given the user selects "Review & Confirm" When they confirm Then detected observances are applied and stored with an explicit consent flag that can be revoked Given the user revokes consent later When they save Then detection-based observances are removed and the account reverts to manual settings
Traveler & Multi-Location Overrides
Given a user sets a travel period with destination locale and timezone When scheduling suggestions are generated for dates within that period Then observance windows are computed using the destination's rules for those dates Given a recurring meeting spans before, during, and after travel When observance times shift at the destination Then instances during travel remain outside hard blocks per destination rules without moving instances outside the travel period Given a team includes members across multiple locations When computing team availability Then the engine applies union of hard blocks across participants and intersection of soft preferences
Preferred Calculation Method & Seasonal Alignment
Given a user selects a specific calculation method for time-based observances (e.g., ISNA, MWL) When observance windows are computed Then times reflect the selected method and match a reference library within ±2 minutes for the user’s coordinates Given a team default calculation method is set When a user has no personal selection Then the team default is applied; when a personal selection exists, it overrides the team default Given seasonal daylight changes occur When recurring events are re-evaluated monthly Then observance windows are recalculated and meetings remain outside hard block windows
Unified Availability Merge (Hours, Focus, Observances)
Given organizational working hours and focus blocks exist alongside observance preferences When building a user's availability profile Then precedence is applied as: Hard Observance Block > Focus Block > Working Hours > Soft Observance Preference Given an API client requests availability for a date range When the merged profile is returned Then each window is labeled with its source (working hours, focus, observance-hard, observance-soft) and soft-risk is exposed as a boolean flag Given a soft preference overlap exists When generating suggestions Then options within the overlap are still offered but marked with a "courtesy risk" indicator in UI and API
Default Micro-Buffers Around Observances
Given a user sets default micro-buffers (e.g., 10 minutes before, 5 minutes after) for observances When a meeting is placed adjacent to an observance window Then the scheduler reserves those buffers by shifting the meeting or proposes the nearest slot that honors buffers Given there is insufficient room to honor buffers around a desired time When generating suggestions Then such slots are excluded and the next viable slot the same day is proposed when available Given a team default buffer is configured and a user has no personal buffer When suggestions are generated Then the team default applies; when the user sets a personal buffer, it overrides the team default Given an event is created via external calendar sync within the buffer threshold When the event is processed Then a warning is surfaced to the organizer and alternates that honor buffers are suggested
Conflict Avoidance & Smart Alternate Suggestions
"As an organizer, I want the system to avoid observances and propose nearby alternatives so that I can schedule respectful meetings without manual back-and-forth."
Description

Extend the suggestion engine to automatically steer away from detected observance windows across all invitees. When a proposed time conflicts, compute and rank nearby alternates that preserve meeting duration, agenda requirements, and fairness across time zones. Incorporate user-defined strictness and buffers, explain conflicts to organizers privately, and present neutral, privacy-safe indicators to other participants. Provide controls to nudge earlier/later windows and batch-generate options for multi-day ranges.

Acceptance Criteria
Avoid conflicts across all invitees
Given a meeting request with participants who have detected observance windows and configured working hours And organizer-defined strictness and pre/post buffers are set When the suggestion engine generates time slots Then no suggested slot overlaps any invitee's observance window expanded by the configured pre/post buffers And no suggested slot falls outside any invitee's configured working hours unless no conflict-free slot exists within the requested range And if no conflict-free slot exists, the engine returns the closest available slots and marks them as less ideal for the organizer with a private explanation of the constraints causing the conflict
Rank nearby alternates preserving duration, agenda, and fairness
Given a proposed time conflicts for one or more invitees When alternate times are computed Then at least 5 alternates within the organizer-selected search horizon (default 24 hours) are returned if available And each alternate preserves the requested meeting duration and any agenda constraints requiring contiguous time blocks And alternates are sorted by a score that prioritizes: zero observance conflicts, adherence to buffers, being within working hours for all invitees, minimal focus-block/travel conflicts, and cross-timezone fairness (minimizing maximum local inconvenience) And for the top 3 alternates, the organizer can view a brief "why suggested" summary including the fairness rationale
Strictness levels and custom buffers
Given strictness is set to Lenient, Standard, or Strict and custom pre/post buffers are configured in minutes When generating suggestions Then no suggestion starts within the pre-buffer before an observance window or ends within the post-buffer after it for any invitee And increasing strictness reduces or maintains (never increases) the set of eligible slots And setting pre/post buffers to 0 permits slots that touch but do not overlap observance windows And changes to strictness or buffers take effect immediately upon recomputation
Private conflict explanations to organizer; neutral indicators to others
Given a user is the meeting organizer When a requested time conflicts for one or more invitees Then the organizer sees a private explanation listing the affected invitees, observance type, local date/time, and the buffer that caused the conflict And given a user is an invitee who is not the organizer When viewing the same time Then they see only a neutral indicator such as "Not ideal for one or more participants" with no names, locales, or observance types revealed And switching between organizer and non-organizer roles shows the expected difference in detail
Nudge earlier/later controls and multi-day batch generation
Given the suggestions panel is open When the organizer clicks Nudge earlier or Nudge later by a chosen increment (e.g., 15/30/60 minutes) Then a new set of suggestions is generated shifted by the increment while preserving duration and all constraints And given the organizer selects a multi-day range (e.g., next 5 business days) and an options-per-day count When Generate is clicked Then the engine returns up to the requested number of conflict-free options per day, labeled with local times per invitee And generation completes within 2 seconds for up to 10 invitees over a 5-day range
Batch fairness validation across time zones
Given invitees span at least three time zones with configured working hours When 10 alternate suggestions are generated Then the top-ranked alternate keeps all invitees within their configured working hours if such a slot exists within the search horizon And across the top 5 alternates, the maximum deviation from each invitee’s midpoint of working hours is minimized compared to lower-ranked options And no single invitee appears outside their working hours in more than 2 of the top 5 options unless no within-hours solution exists
Recurring series stays observance-aware over seasonal changes
Given a weekly recurring meeting with observance-aware avoidance enabled And daylight saving time or seasonal observance times shift for any invitee When the engine recomputes future occurrences Then any occurrence that now conflicts is moved to the nearest conflict-free slot within the organizer’s defined window while preserving duration and agenda And the organizer is notified privately with a summary and can accept the move or choose from at least 3 alternates And non-organizers receive only a neutral notice without personal observance details
Micro-Buffer Configuration & Enforcement
"As a frequent meeting participant, I want automatic buffers around observances so that I’m not rushed immediately before or after important practices."
Description

Introduce configurable pre- and post-observance buffers at user, team, and meeting levels to allow travel, preparation, or recovery time. Ensure the scheduling engine enforces these buffers when generating suggestions and when validating manual time selections. Provide sensible defaults by locale and observance type, detect back-to-back risks across observance boundaries, and surface unobtrusive nudges to widen gaps where needed.

Acceptance Criteria
User-Level Micro-Buffer Configuration
- Given I am a signed-in user with a defined locale, When I open Observance & Buffers settings, Then I can set default pre-buffer and post-buffer values in whole minutes (0–120) per observance type. - Given I enter an out-of-range or non-numeric value, When I click Save, Then the field is highlighted with an error and Save is disabled until corrected. - Given I set valid values and click Save, Then the values persist, are versioned to my profile, and are used immediately by the scheduling engine. - Given I have no custom values, When I view the settings, Then locale/season-based defaults are displayed and used.
Team and Meeting-Level Buffer Overrides and Precedence
- Given I am a team admin, When I set team-level buffers per observance type, Then they apply to all team members by default. - Given a meeting has meeting-level buffer settings, Then those settings override team- and user-level buffers for that meeting. - Given no meeting-level settings but team-level exist, Then team-level buffers override user-level for team members; non-team invitees use their own effective values. - Given a meeting includes multiple participants with different effective buffers, When computing compliance, Then the maximum required pre-buffer and post-buffer across all participants is enforced. - Given I open a meeting’s settings, Then the effective buffer values and their source (meeting/team/user/default) are visible.
Suggestion Engine Enforces Observance Buffers
- Given participants and their observance calendars are known, When generating suggestions, Then no suggestion starts before an observance end plus the enforced post-buffer or ends after an observance start minus the enforced pre-buffer for any participant. - Given the nearest feasible time occurs after an observance window, When suggesting times, Then the first suggestion is at or after end + max post-buffer, preserving requested duration. - Given work hours and buffers leave no valid slot on the chosen day, Then zero suggestions are returned and the UI explains that buffers/observances caused the conflict. - Given alternates exist within the scheduling horizon, Then Timeglue returns up to N nearest compliant alternates ordered by proximity, where N is the product setting.
Manual Time Selection Validation and Auto-Adjust
- Given I manually select a start/end time that violates any enforced buffer, When I attempt to save, Then the save is blocked with an inline message naming the participant and deficit minutes. - Given my selection meets enforced buffers but is within a configured nudge threshold (e.g., <5 minutes margin), When I release the drag, Then a non-blocking nudge appears offering “Apply buffer” and showing the adjusted start/end. - Given I click “Apply buffer”, Then the selection snaps to the nearest compliant time and duration is preserved; if not possible, a reason is shown. - Given I dismiss the nudge, Then the meeting is saved as selected and the decision is recorded in the audit log.
Locale- and Observance-Type Default Buffers
- Given a user has no custom buffer settings, When their locale and observance profile are known, Then pre/post default buffers are auto-populated per locale and observance type, including seasonal variants where defined. - Given defaults are applied, When the user views settings, Then each default shows its source (e.g., “Locale: ID – Ramadan”) and can be overridden. - Given the locale is changed, When the user revisits settings, Then defaults recalc and proposed changes are previewed before applying.
Back-to-Back Risk Detection Across Observance Boundaries
- Given a meeting ends less than the enforced pre-buffer before an observance start or starts less than the enforced post-buffer after an observance end, When the meeting is created or edited, Then a risk badge appears and suggestions to shift by the exact deficit minutes are offered. - Given risk is detected, When generating adjacent meeting suggestions, Then the engine avoids proposing slots that would create such risks for any participant. - Given the user accepts the suggested shift, Then the meeting time updates and the risk badge disappears.
Recurring Meetings Preserve Buffers as Observance Times Shift
- Given a recurring meeting spans dates where observance times change, When future occurrences are recalculated, Then each instance is adjusted to maintain enforced buffers for all participants within their work hours. - Given an occurrence cannot be adjusted without violating buffers or work hours, Then the system proposes the nearest compliant day/time or marks the instance as “skipped” with notification to organizer and attendees. - Given auto-adjust is enabled for the series, When recalculations occur, Then updates are applied automatically and a summary of changes is sent; otherwise they are queued for organizer approval.
Recurring Series Daylight Alignment
"As an organizer of recurring meetings, I want future instances to stay clear of observances as daylight changes so that our plan remains courteous over time."
Description

Maintain courtesy for recurring meetings as daylight and seasons change by recalculating each occurrence against evolving observance windows and DST shifts. Support rules such as “anchor to remain outside observance” and offer organizer previews of upcoming adjustments with one-click apply to all or selected occurrences. Generate calendar exceptions, notify impacted participants, and preserve agenda integrity and meeting length while minimizing disruption.

Acceptance Criteria
DST Recalculation Preserves Duration and Courtesy
Given a recurring series spans at least two time zones and a DST change occurs in any participant locale before a future occurrence When the system recalculates the first occurrence after the DST change Then the occurrence is rescheduled using updated offsets while preserving the exact meeting duration (±1 minute) And the occurrence is entirely outside each participant’s observance windows plus configured micro-buffers And the occurrence stays within each participant’s working hours and avoids focus blocks and local holidays And the applied time shift is the smallest absolute delta in minutes necessary to satisfy all constraints And the occurrence is marked as adjusted in the organizer preview
Anchor Outside Observance Rule Enforcement
Given a recurring series has the rule "anchor to remain outside observance" enabled and an observance shift overlaps the planned time When recalculating a future occurrence Then the system selects the nearest available start time on the same calendar day that lies completely outside the observance window plus configured micro-buffers And the original meeting duration is preserved And the weekday remains unchanged And if no valid slot exists on that day, the occurrence is flagged "Needs Organizer Decision" and up to three nearest alternatives are proposed ranked by minimal total participant time shift
Organizer Preview and One‑Click Apply
Given the system detects upcoming occurrences impacted by observance changes or DST When the organizer opens the series adjustment preview Then the UI lists each affected occurrence with original time, proposed time, reason (DST/observance), participant impact summary, and time delta in minutes And the organizer can select "Apply to all" or choose specific occurrences and click "Apply" And upon confirmation, only the selected occurrences are updated and an audit entry is recorded with timestamp, actor, and affected instance IDs
Calendar Exceptions Generation and Integrity
Given the organizer applies adjustments to selected occurrences When syncing updates to connected calendars Then the system creates per-occurrence exceptions within the original recurring series (not independent stand-alone events) And preserves series UID, conferencing link, location, attendees, agenda, and attachments unchanged And modifies only start and end times for adjusted occurrences And surfaces a success/failure status per participant calendar in the same sync session without duplicating events
Participant Notifications with Localized Clarity
Given adjustments have been applied to one or more occurrences When notifications are dispatched Then each participant receives one notification per adjusted occurrence showing original time, new time, local time zone, delta in minutes, and the reason (DST/observance), including any micro-buffer details And the participant’s calendar reflects an update to the existing instance rather than a new event And participants who previously declined are not re-invited unless the organizer explicitly opts to re-invite And no notifications are sent for occurrences shown only in preview and not applied
Micro‑Buffer Application Without New Conflicts
Given optional micro-buffers (e.g., 10 minutes before, 5 minutes after) are configured for the series When proposing alternate times to avoid observance windows Then the chosen time plus micro-buffers must not overlap observance windows, working hour boundaries, focus blocks, or organizer-busy events And the final scheduled occurrences include the configured micro-buffers And if micro-buffers would introduce a conflict while the meeting itself would not, the system proposes the next best time satisfying both meeting and buffer constraints or flags the buffer as unsatisfiable and prompts the organizer to adjust
Minimal Disruption Alternate Selection
Given multiple valid alternate times exist for an affected occurrence When selecting a proposed time Then the system chooses the option that minimizes the sum of absolute time deltas in minutes across all participants relative to the prior schedule And breaks ties by preferring the original weekday and earlier times within acceptable hours And never moves the meeting to a different calendar day unless no valid same-day option exists
Privacy-Preserving Courtesy UX
"As a privacy-conscious user, I want the product to steer scheduling respectfully without revealing my observance details to others so that my personal practices remain private."
Description

Design UI and messaging that quietly guide organizers away from observances without exposing sensitive personal information. Show neutral “unavailable” indicators in shared views, provide specific context only to the affected user and, when appropriate, the organizer, and require explicit consent for overrides that intrude on hard-blocked observances. Localize copy, meet accessibility standards, and log courtesy-related changes for auditability while storing only minimum necessary metadata.

Acceptance Criteria
Neutral Unavailable Indicators in Shared Views
Given a shared scheduling page with at least one participant having an active observance window When an organizer or external invitee views the shared availability timeline/matrix Then all time slots overlapping the observance are marked as "Unavailable" without naming or describing the observance And no tooltips, popovers, or hover text reveal observance details in shared views And the shared view does not display user-specific notes, reasons, or icons that imply religion or observance type And screen readers in shared views announce the slot as "Participant unavailable" (locale-adjusted) without exposing observance details
Context Visibility Scoped to Affected Parties
Given a signed-in user with an active observance window When the user views their personal calendar or scheduling panel Then the user sees specific context (observance name/notes) per their own preference settings And by default, organizers viewing that user see only a neutral label (e.g., "Protected time") without details And if the user explicitly enables "Share limited context with organizers," organizers see a generic category label (e.g., "Religious/observance block") but no personal notes or sensitive metadata And revoking the sharing toggle immediately removes organizer access to the limited context
Consent-Gated Scheduling During Hard Observances
Given an organizer selects a time that overlaps a participant’s hard-blocked observance When the organizer attempts to send the invite Then the system blocks sending and presents a "Request exception" flow instead of scheduling And upon sending an exception request, the affected participant receives a consent prompt with options: Approve once, Propose alternate, Decline And the event cannot be created in that slot unless the participant explicitly approves And consent decisions are time-limited (configurable expiry) and logged with actor, timestamp, request ID, and outcome And if consent expires or is declined, the organizer is shown nearby alternate times without revealing observance details
Localization and RTL Support for Courtesy Copy
Given the app locale is set to any supported language (including RTL locales) When courtesy-related UI text (unavailable labels, exception prompts, notifications) is rendered Then all copy appears in the selected locale with correct pluralization, date/time formatting, and punctuation And RTL locales mirror layout and iconography appropriately and maintain reading order And no English fallback appears in supported locales; if a string key is missing, a tracked fallback is shown and an i18n missing-key event is logged And dynamic text does not overflow or truncate critical information in common viewports (≥320px width)
Accessibility Compliance for Courtesy Elements
Given a user navigates with keyboard-only and a screen reader When interacting with courtesy indicators, prompts, and banners Then all actionable elements are reachable in a logical tab order and have visible focus states And each indicator/prompt has an accessible name and role; status changes are announced via ARIA live regions without duplicative announcements And text and icon contrast meet WCAG 2.2 AA (≥4.5:1 for text, ≥3:1 for large text/icons) And no content is conveyed by color alone; there is a non-color cue for "Unavailable" And all interactions are operable without pointer gestures and without time limits that cannot be extended
Minimal Metadata Storage and Audit Logging
Given any courtesy-related action occurs (create/edit hard/soft observance block, toggle context sharing, request/approve/decline exception) When the system records an audit entry Then the log stores only actor ID, action type, timestamp, resource ID, and outcome; it excludes observance names, religion, notes, or geolocation beyond timezone/locale And audit entries are encrypted at rest, access-controlled to admins/auditors, and retrievable via audit API/UI with pagination and filters And metadata retention is configurable with a default of 12 months and a hard maximum of 24 months; redaction jobs purge expired entries And unit/integration tests verify redaction, field whitelisting, and access control for audit retrieval
Calendar Provider Sync & ICS Support
"As an integrations engineer, I want observance-aware availability to sync cleanly with major calendars so that scheduling remains consistent across tools."
Description

Synchronize observance windows and applied buffers to connected calendars (Google, Microsoft) as generic busy periods and ensure scheduling suggestions respect provider-side visibility and sharing settings. Update ICS feeds with neutral labels, handle provider-specific recurrence/exception behavior, and reconcile changes when external edits occur. Avoid exporting sensitive classifications, and provide webhooks to notify Timeglue when provider-side data impacts observance-aware availability.

Acceptance Criteria
Google Busy Sync for Observance Windows and Buffers
Given a user has connected Google Calendar and enabled Observance Aware with defined daily/weekly windows and 10-minute pre/post buffers When a sync is triggered by a config change, initial connect, or scheduled refresh Then Timeglue creates or updates one busy event per contiguous window in the user's primary Google calendar within 2 minutes And each event has subject "Unavailable", visibility Private, transparency Opaque (Busy), and no location, description, attendees, or categories And buffers are included per the user's buffer setting (merged into the block or as adjacent blocks) And overlapping or adjacent blocks with gaps of 5 minutes or less are merged into a single busy event And deleting or disabling an observance in Timeglue removes or shortens the corresponding Google event within 60 seconds And no more than one provider event exists per window, verified by a stable extendedProperty externalId
Microsoft 365 Busy Sync for Observance Windows and Buffers
Given a user has connected Microsoft 365 Calendar and enabled Observance Aware with defined windows and pre/post buffers When a sync is triggered by a config change, initial connect, or scheduled refresh Then Timeglue creates or updates one busy event per contiguous window in the user's default calendar within 2 minutes And each event has subject "Unavailable", sensitivity Private, showAs Busy, isAllDay false unless the window is all-day, and no body, location, attendees, or categories And buffers are included per the user's buffer setting (merged into the block or as adjacent blocks) And overlapping or adjacent blocks with gaps of 5 minutes or less are merged into a single busy event And deleting or disabling an observance in Timeglue removes or shortens the corresponding Microsoft event within 60 seconds And no duplicate provider events exist per window, verified by a stable x-timeglue-id on the event
ICS Feed Neutrality and Privacy
Given an authenticated subscriber fetches the user's Timeglue ICS feed for observance-based unavailability When the feed is generated or refreshed Then each block is a VEVENT with SUMMARY "Unavailable" and CLASS:PRIVATE, with no DESCRIPTION, LOCATION, CATEGORIES, ATTENDEE, or ORGANIZER fields And the feed includes VTIMEZONE with correct TZID and uses RRULE/EXDATE to represent recurrence and exceptions And updates reflecting a Timeglue change appear in the feed within 2 minutes And the feed supports ETag and If-Modified-Since and returns HTTP 304 when unchanged And the ICS validates against RFC 5545 (no structural errors)
Recurrence and Exception Handling Across Seasonal Shifts
Given a recurring observance whose local start/end shift over time due to sunset or seasonal changes When synchronization runs for Google and Microsoft over a 90-day period with at least three shifts Then provider-side events reflect each date's correct local wall-time using occurrences and exceptions so that no occurrence remains at an obsolete time And exceptions are added or updated without recreating the entire series where the provider supports it And meeting suggestions generated by Timeglue do not land inside any observance window or its configured buffers on any shifted date And editing a single occurrence on the provider does not alter other occurrences and is preserved on resync
External Edit Reconciliation Policy
Given a provider-side busy event created by Timeglue is externally edited (time, subject, privacy) or deleted When Timeglue receives a provider webhook or detects the change on the next poll within 5 minutes Then reconciliation runs using the user's policy And if policy is "Enforce Timeglue" then the external change is reverted within 60 seconds and the user is notified And if policy is "Allow Provider Edits" then the external change is adopted and internal availability updates accordingly And reconciliation is idempotent (no duplicate events), with conflicts logged to an audit trail And suggestions generated after reconciliation reflect the final state
Provider Sharing and Suggestion Privacy Respect
Given the user's provider calendar has sharing set to Free/Busy-only or limited details When Timeglue-synced busy blocks are viewed by external parties or used to generate scheduling suggestions Then external viewers see only Busy status and a neutral subject "Unavailable" (or no subject where the provider enforces Free/Busy-only) And no observance-specific terms, locations, notes, or categories are exposed via provider UI, ICS, or API beyond what sharing settings permit And Timeglue does not propose times that overlap provider Busy status and does not require elevated visibility to avoid observance windows
Webhook Notifications for Provider-Side Availability Changes
Given provider-side data changes that impact availability (event create/update/delete, time zone change, sharing setting change) When the provider emits a change notification or polling detects a delta Then Timeglue emits a webhook to the subscriber within 60 seconds on average (5 minutes worst-case under throttling) And the webhook payload includes eventType, provider, calendarId, externalEventId, changeType, affectedTimeRange, and a signed HMAC-SHA256 signature header And retries use exponential backoff for up to 24 hours with an idempotency key to prevent duplication And 2xx responses mark delivery success; non-2xx trigger retry; failures are surfaced in an admin log

School Run Shield

Protects common family logistics like school drop‑off/pick‑up with adjustable buffers per region. When a proposed slot touches those windows, Culture Clock flags it and offers aligned alternates that preserve work hours. Supports one‑off exceptions for special events without rewriting the whole series.

Requirements

Region-Scoped School Run Windows & Buffers
"As a parent on a distributed team, I want to set school drop‑off/pick‑up windows with buffers per region so that meetings don’t overlap family logistics when I’m in different time zones."
Description

Enable users to define recurring school drop‑off/pick‑up windows with adjustable pre/post buffers per region and time zone. Windows can be configured by weekday and date range, are DST-aware using IANA time zones, and can inherit from team defaults while allowing personal overrides. The system treats these as protected blocks that take precedence over suggested meeting times, evaluated alongside existing work hours, holidays, and focus blocks. Supports multi-region scenarios (e.g., travel) with prioritized active region selection and automatic re-alignment of windows. Includes a draggable timeline UI for painting protected periods, precise time inputs for buffers, and validation to prevent overlap or impossible schedules. Data is stored as versioned policies to allow rollback, with audit metadata for changes.

Acceptance Criteria
Define Recurring Region-Scoped School Run Windows with Buffers (DST-Aware)
Given I am configuring Region "Europe/Berlin" with IANA time zone Europe/Berlin and no existing personal school-run policy When I create a recurring drop-off window 08:15–09:00 and a pick-up window 15:00–15:30 for Monday–Friday within date range 2025-09-01 to 2025-12-20 and set buffers (drop-off: pre 15m, post 10m; pick-up: pre 20m, post 15m) Then the policy is saved with timezone Europe/Berlin, weekdays Mon–Fri, date range 2025-09-01..2025-12-20, and the specified buffers And the timeline preview shows protected periods inclusive of buffers for each applicable day And across any DST transition in this time zone, the protected windows remain at the same local wall-clock times with buffers applied And the API returns the policy with IANA time zone, per-occurrence UTC offsets, and the next 10 protected instances computed correctly
Inherit Team Defaults with Personal Overrides per Region
Given a team default school-run policy exists for Region Europe/Berlin (drop-off 08:30–09:15 with 10m/10m buffers; pick-up 15:30–16:00 with 10m/10m buffers) And my personal policy is set to inherit team defaults When I override Tuesday pick-up to 16:15–16:45 with pre 0m and post 15m Then Tuesday uses the personal override while other weekdays follow the team defaults And a later change to the team default Wednesday pick-up updates my Wednesday schedule but does not affect my Tuesday override And the UI clearly indicates inherited vs overridden values per weekday And using "Revert to team default" on Tuesday removes the override and restores inheritance
Protected Blocks Take Precedence Over Meeting Suggestions
Given my active region policy defines protected school-run periods inclusive of pre/post buffers And my work hours are 09:00–17:00 and there are no holidays or focus blocks at 10:00–11:00 on 2025-09-23 When I request 30-minute meeting suggestions for 2025-09-23 Then no suggestion overlaps any protected school-run period or its buffers And any proposed time that touches the protected boundary is excluded And at least three alternate slots are returned within work hours that avoid holidays and focus blocks And if I manually input a time that overlaps protection, the UI flags a conflict and offers aligned alternates
Draggable Timeline and Precise Buffer Inputs
Given the region day-view timeline renders 00:00–24:00 with 5-minute snapping When I drag from 07:45 to 08:55 on Thursday to define a drop-off window Then the core window records 07:45–08:55 for Thursday And when I enter buffers of pre 15m and post 10m using numeric inputs with 1-minute precision Then the preview shows protection from 07:30 to 09:05 for Thursday And keyboard adjustments move boundaries by 1 minute (arrow) and 15 minutes (Page Up/Down) And on mobile, drag and tap-to-adjust interactions can set the same times within ±1 minute And unsaved changes are indicated and can be discarded or saved
Validation Prevents Overlaps and Impossible Schedules
Given an existing Thursday pick-up window 15:00–15:30 with pre 30m and post 30m buffers When I attempt to add a drop-off window 14:45–15:15 with pre 30m and post 0m on the same day in the same region Then a validation error prevents saving because protected periods would overlap And the error highlights the conflicting windows and specifies the overlap range And suggested fixes allow reducing buffers or merging windows; selecting a fix resolves the conflict and allows save And attempts to set buffers to negative values or non-integer minutes are rejected with inline errors And if the resulting protected period would be invalid (e.g., end before start without an explicit "Spans midnight" setting or duration > 24 hours), saving is blocked with an explanatory error
Multi-Region Travel: Active Region Priority and Auto Realignment
Given I have Region A (America/Los_Angeles) and Region B (Asia/Singapore) policies defined with priority A > B When I add a travel period to Singapore from 2025-10-10 to 2025-10-20 Then during that date range the active region is Asia/Singapore and protected windows align to local Singapore time And outside that date range the active region reverts to America/Los_Angeles And if two travel periods overlap, the higher-priority region is selected for the overlapping dates And meeting suggestions and conflict checks use the active region's policy for the evaluated date
Versioned Policies with Audit Trail, One-Off Exceptions, and Rollback
Given my current school-run policy is version v12 and changes are audited with user, timestamp, and reason When I create a one-off exception on 2025-12-12 from 15:30 to 16:00 to allow a special event Then an exception record is stored with date/time scope and reason, and the recurring series remains unchanged And scheduling during that exact exception window is allowed while protection before and after remains enforced And when I later adjust Tuesday drop-off and save, a new policy version v13 is created with audit metadata And I can view a history of versions with diffs between v12 and v13 And when I rollback to v12, the prior settings are restored and availability is re-evaluated accordingly, while existing exceptions persist unless explicitly removed
Culture Clock Conflict Flagging
"As an organizer, I want the system to flag when a proposed meeting touches someone’s school‑run window so that I avoid scheduling conflicts without manual checks."
Description

Detect and flag proposed meeting slots that touch protected school‑run windows across all scheduling surfaces (timeline composer, calendar overlay, booking flow, and API). Indicators include inline badges, color overlays, and reason codes specifying which participant(s) are affected and which policy triggered the flag. Provides configurable behaviors: block hard conflicts, warn on adjacent slots within buffer, and require acknowledgment or alternative selection. Ensures accessibility (ARIA announcements, high-contrast visuals) and localizes messages. Logs every flag decision with inputs (participants, regions, constraints considered) for supportability and analytics.

Acceptance Criteria
Timeline Composer: Conflict Flagging and Alternate Suggestions
Given the timeline composer is open with School Run Shield enabled and participants across multiple regions When the user paints a slot that overlaps any participant’s protected school‑run window Then an inline badge labeled "School Run Conflict" appears on the slot with reason code "school_run_overlap" and lists the affected participant(s) And a high‑attention conflict overlay renders only over the overlapping portion of the slot And at least 3 alternate times within the next 5 business days are suggested that avoid protected windows and buffers and preserve each required participant’s work hours And each alternate displays local times per participant and a Replace action When the user selects Replace on an alternate Then the original slot is updated to the alternate and the conflict badge and overlay are removed
Calendar Overlay: Visual Indicators and ARIA Announcements
Given the calendar overlay is open and a proposed slot is flagged as conflict or adjacent‑buffer warning When the user focuses or hovers the flagged slot or reaches it via keyboard navigation Then a high‑contrast visual indicator (contrast ratio ≥ 4.5:1) with a conflict icon is shown And an ARIA live polite announcement is emitted within 500 ms describing severity, affected participant names, and policy label And the conflict chip is focusable, has role="button", and is actionable via Enter/Space to open details And the details popover is reachable via keyboard and exposes the same information as visually presented
Booking Flow: Configurable Block/Warning and Acknowledgment
Given org policy is set to "Block hard conflicts" When the user selects a time that overlaps a protected school‑run window for any required participant Then the Confirm/Book action is disabled and an inline error states the reason with code "school_run_overlap" And the user cannot complete booking until a non‑conflicting time is selected Given org policy is set to "Warn on adjacent within buffer" When the user selects a time within the configured buffer of a protected window Then a modal presents the warning with reason code "school_run_buffer_adjacent" and shows buffer distance in minutes And proceeding requires explicit acknowledgment (checkbox) which is recorded on the event as acknowledged=true with timestamp and user id And if the user cancels, the flow returns to time selection with alternate non‑conflicting suggestions displayed
API: Structured Conflict Flags and Localization
Given a client POSTs candidate times to /v1/schedule/evaluate with participants and regions When any candidate overlaps a protected window or falls within the buffer Then the response is 200 and includes a flags[] array where each flag has: code, severity (hard|warn), participants[].id, policy_id, overlap_or_buffer_window.start/end (ISO 8601 with zone), buffer_distance_minutes (if applicable), and surfaces[] And message.localized includes entries for at least en-US, and the server honors Accept-Language for the selected message And non‑conflicting candidates return an empty flags[] And the endpoint responds within 800 ms for up to 10 participants and 50 candidates
Adjacent Slot Buffer Handling
Given a region’s buffer is configured to N minutes and the protected window is [S,E] When a proposed slot ends at time T where 0 ≤ (S − T) ≤ N Then a warn‑severity flag is raised with code "school_run_buffer_adjacent_before" and buffer_distance_minutes = (S − T) When a proposed slot starts at time T where 0 ≤ (T − E) ≤ N Then a warn‑severity flag is raised with code "school_run_buffer_adjacent_after" and buffer_distance_minutes = (T − E) When N = 0 Then no adjacent‑buffer warning is produced When a proposed slot overlaps [S,E] by ≥ 1 minute Then a hard‑severity flag is raised with code "school_run_overlap"
Participant-Specific Reason Codes and Presentation
Given a proposal with multiple participants across regions results in a conflict When the conflict details are displayed on any surface (composer, overlay, booking) Then the details list includes only affected participants with: display name, region/time zone, local conflict time span, policy label, and reason code And the list is grouped by severity (hard before warn) and sorted by earliest local conflict time within each group And unaffected participants are not listed
Decision Logging for Supportability and Analytics
Given any conflict evaluation occurs on any surface or via API When a flag decision is made (hard block, warn, acknowledged, or resolved) Then a log entry is written with correlation_id, user/session id, timestamp (UTC), surface, participants (ids and regions), proposed slot start/end (ISO 8601 with time zones), constraints considered (work hours, holidays, focus blocks, school‑run windows, buffers), evaluation result (severity, reason codes), policy snapshot id, locale used, and outcome And log writes retry up to 3 times on transient failure and never block UI actions And logs are retrievable via admin export with PII limited to participant ids and display initials unless an authorized support role is used
Aligned Alternate Slot Suggestions
"As a facilitator, I want Timeglue to offer fair alternate slots that avoid school‑run windows and stay within work hours so that I can reschedule quickly."
Description

When a conflict is detected, generate a ranked list of alternate meeting slots that preserve all participants’ defined work hours and avoid protected school‑run windows, focus blocks, and holidays. The suggestion engine evaluates cross‑region overlaps, fairness (rotating early/late burden), participant availability constraints, and organizer preferences (meeting length, earliest/latest bounds). Results are limited to a configurable number of options, explainable (each suggestion shows why it works), and returned via UI and API. Includes tuning parameters (fairness weight, buffer strictness) and fallbacks when no perfect slot exists (e.g., propose the least intrusive option with clear disclosure).

Acceptance Criteria
Ranked Alternates Within All Constraints
Given a meeting request of length L with participants who have defined work hours, protected school-run windows (with regional buffers), focus blocks, and holidays, and organizer earliest/latest bounds When the requested slot conflicts with any protected window or constraint Then the suggestion engine returns alternate slots only if each slot: - starts and ends within every participant’s work hours - does not intersect any participant’s school-run window including buffer - does not intersect any participant’s focus blocks - does not fall on any participant’s holiday - respects the organizer’s earliest and latest bounds - has duration exactly L
Fairness Rotation and Scoring Across Regions
Given fairnessWeight is configurable and a history of prior meeting times exists When generating suggestions Then ranking incorporates fairness such that: - If fairnessWeight = 0, ranking ignores historical burden and matches baseline constraint satisfaction score - If fairnessWeight > 0, options that reduce cumulative off-core minutes for highly burdened participants rank higher - Off-core minutes are minutes scheduled within the first 25% or last 25% of a participant’s workday - For slots with equal baseline scores, the slot with lower total off-core minutes across participants ranks higher - Across a series, when viable, early/late burdens are rotated among participants (no participant has early/late in two consecutive occurrences if alternatives exist)
Explainable Suggestions via UI and API
Given suggestions are generated When they are returned to the client Then each suggestion includes an explanation payload containing: - reasons[] with at least: within_work_hours, avoids_school_run, avoids_focus_blocks, avoids_holidays - perParticipant[] items with: participantId, timezone, localStart, localEnd, meetsWorkHours, avoidsSchoolRun, avoidsFocusBlocks, avoidsHolidays - score.total and score.components (e.g., fairness, overlapQuality, preferenceMatch) And the UI displays a "Why this works" panel showing the same reasons And the API returns identical fields for each suggestion
Configurable Limit and Deterministic Ordering
Given maxSuggestions = M and identical inputs (participants, constraints, preferences, fairnessWeight, bufferStrictness) When suggestions are requested repeatedly Then the engine returns min(M, number_of_valid_slots) unique suggestions And ordering is deterministic: sorted by score.total (desc), then UTC start (asc), then stable slotId (asc) And no two suggestions share the same start time in UTC
Fallback Suggestion When No Perfect Slot Exists
Given no time slot satisfies all constraints simultaneously When suggestions are generated Then at least one fallback suggestion is returned with fallback = true and a disclosure message And organizer earliest/latest bounds are never violated And violations are minimized by: - minimizing total violation minutes across all participants - preferring to relax focus blocks before school-run buffers, before holidays, before work hours And if bufferStrictness = adaptive, the engine may reduce school-run buffers by up to B minutes (configurable) but never overlaps the core school-run window And the explanation lists each violated constraint, impacted participants, and minutes overlapped
One-Off Exception Handling for Protected School-Run Windows
Given a participant has a one-off exception for a school-run window on date D When generating suggestions for date D Then that window is treated as available only for that participant on D And recurring protections remain unchanged for other dates And the explanation includes exceptionApplied = true with the exception reference id for that participant
One‑Off Exceptions and Temporary Overrides
"As a parent, I want to allow a one‑time exception during a pickup day for a special event so that we can meet without rewriting my entire schedule."
Description

Allow users to create single‑date or short‑range exceptions that temporarily relax or modify school‑run protections without altering the underlying recurring series. Exceptions support specifying date(s), adjusted times/buffers, reason, and auto‑expiration. The system clearly indicates an active exception in the timeline, applies it to conflict checks and suggestions for the affected period only, and reverts automatically after expiry. Includes guardrails based on team policy (who can create, required approvals) and maintains an auditable history of overrides with user, timestamp, and scope.

Acceptance Criteria
Single-Date Exception Applies to Conflict Checks and Suggestions
Given a user with permission selects a single date D and sets modified school-run buffers and a non-empty reason And a recurring series exists for that region When the user saves the exception Then the exception is applied only on date D for conflict checks and meeting suggestions And the recurring series remains unchanged on all other dates And attempts to schedule within the modified window on D respect the new buffers
Short-Range Exception Auto-Expires and Reverts Series
Given org policy sets a maximum exception range of 7 days And a user creates an exception from start date S to end date E within 7 days inclusive When current time passes E at the exception region’s local end time Then the exception auto-expires and no longer affects conflict checks or suggestions And the timeline reverts to showing only the recurring series for dates after E
Timeline Clearly Indicates Active Exception
Given an exception is in effect for a visible portion of the timeline When the user views the timeline Then an “Exception Active” indicator is displayed on the affected window And hovering or focusing the indicator reveals reason, creator, effective dates/times, and scope And the indicator is keyboard focusable and exposes an accessible name including the reason
Policy Guardrails and Approval Workflow Enforcement
Given team policy requires approval from role R and the requester lacks self-approval rights When the requester submits an exception covering dates D1..Dn Then the exception status is Pending and does not alter conflict checks or suggestions until approved And designated approvers are notified When an approver approves the request Then the exception becomes Active and immediately affects conflict checks and suggestions for D1..Dn And if the request is rejected, no changes are applied and the requester is notified with the rejection reason
Auditable History of Overrides
Given an exception is created, approved, modified, deleted, or expired When any of these events occur Then an audit entry is written with action type, actor, timestamp (UTC), affected user/team, region, date range, buffers before/after, reason, and approval reference And audit entries are immutable and can be retrieved via admin reporting filtered by date range and user
Overlapping Exceptions Precedence Rules
Given two exceptions overlap for the same user and region When determining which buffers to apply for the overlapping period Then the exception with the narrower time window takes precedence for that overlap And if windows are equal, the most recently approved exception takes precedence And at submission time the system warns about the overlap and shows which exception will prevail
Region-Scoped Exceptions in Cross-Time-Zone Scheduling
Given a cross-region meeting is being scheduled and an exception exists for participant P in region R When generating conflict checks and suggestions Then only P’s school-run buffers are modified per the exception during its effective period And other participants’ protections and work hours remain unchanged And suggested windows honor all participants’ constraints simultaneously
Smart Link Awareness and Communication
"As a link recipient, I want to understand why certain times are unavailable and see suggested options so that I can book a suitable slot without back‑and‑forth."
Description

Ensure all shared scheduling links respect School Run Shield policies. Public recipients see protected windows as unavailable with a concise, privacy‑preserving explanation banner (e.g., "Protected family time in this region") and contextual suggestions surfaced inline. Attempted bookings that touch protected windows are prevented or rerouted to alternates based on policy. Link tokens carry a snapshot of applicable constraints to ensure consistent behavior even if policies change mid‑flow, and invite/ICS descriptions annotate why certain ranges were excluded. Supports localization, theming, and embed modes while minimizing leakage of personal details.

Acceptance Criteria
Public View: Protected Windows Hidden with Privacy Banner
Given a public scheduling link with School Run Shield active for the recipient’s region When the recipient opens the availability page Then all time slots that overlap protected windows are disabled (unclickable), visually distinct, and excluded from availability counts And a banner is displayed stating "Protected family time in this region" above the calendar And the banner and UI reveal no personal identifiers (no names, no addresses, no exact school names) And disabled slots are not reachable via keyboard focus or client requests (selection attempts are ignored) And behavior is consistent on desktop and mobile responsive layouts
Inline Alternates for Conflicting Selections
Given a recipient selects or hovers a slot that touches a protected window When alternates exist within policy-compliant hours Then at least 2 alternate slots (if available) are surfaced inline, prioritized by earliest acceptable time within the same day or next business day And all alternates respect work hours, holidays, focus blocks, and do not overlap protected windows And if fewer than 2 alternates exist, all valid alternates are shown with a notice "Limited alternates" And selecting an alternate advances to confirmation using the same link token
Policy-Driven Prevention or Reroute on Booking Attempt
Given policy mode is set to "prevent" for slots touching protected windows When a recipient attempts to book such a slot via UI or API Then the booking is blocked, no hold or event is created, and the response shows a privacy-preserving message "This time conflicts with protected family time in this region" And the API returns HTTP 409 with a machine-readable error code (e.g., TG_PROTECTED_WINDOW) Given policy mode is set to "reroute" When a recipient attempts to book such a slot Then the system proposes the nearest acceptable slot(s) within policy and requires explicit confirmation before booking And no tentative events are placed in protected windows
Link Token Snapshot Preserves Constraints Mid-Flow
Given a smart link token captures region, policy mode, work hours, holidays, focus blocks, and protected window definitions at generation time (snapshot) And policy settings change after link creation but before booking completion When the recipient uses the original link within its validity period Then availability rendering, banners, alternates, and enforcement reflect the snapshot, not the latest policy And the booking audit log records snapshot version, region, and token ID And the token has a configurable expiry (default ≥14 days); after expiry, requests fail with HTTP 410 and no booking occurs
ICS/Invite Annotations Explain Exclusions
Given a booking is completed via a smart link When generating ICS and calendar invite artifacts Then the description includes a localized, privacy-preserving note that certain time ranges were excluded due to "Protected family time in this region" And no personal details (names, addresses, exact school references) are included And organizer and attendee see the same annotation text And if protected windows did not influence availability, no annotation is added
Localization, Theming, and Embed Compliance Without Data Leakage
Given the link is opened with a non-default locale and theme in an iframe embed When the page renders Then all feature-related strings (banner, error, alternates, notices) are fully localized to the requested locale And UI honors theme tokens with WCAG AA contrast for text and interactive states And embed mode does not leak protected window boundaries or policy details via URL params, postMessage, or analytics payloads And analytics events exclude PII and do not include exact protected time ranges
Host-Approved One-Off Exceptions Are Honored
Given the host configures a one-off exception for a specific protected window on date D and shares a smart link When a recipient selects a slot within the approved exception bounds Then the slot is shown as available and bookable only within the exception bounds And the banner remains generic without disclosing exception details And the resulting event metadata sets approved_exception=true and ICS notes "Booked during a host-approved exception" (localized) And the exception does not alter availability outside date D or beyond the exception’s scope
Team Policies and Role‑Based Controls
"As an admin, I want to set default school‑run protection and override permissions per region so that our teams stay family‑friendly while remaining flexible."
Description

Provide administrators with organization‑level defaults and region templates for school‑run protections, including standard windows, buffer recommendations, and enforcement levels (block vs warn). Roles and permissions define who can view, edit, or override policies, with optional approval workflows for exceptions. Includes privacy controls to obfuscate personal details (display generic reasons instead of exact labels) and visibility rules (e.g., policy presence vs exact times). Offers import/export of policy templates, change notifications, and audit/compliance logs integrated with SSO/SCIM groups.

Acceptance Criteria
Org Defaults and Region Templates Setup
Given I am an Org Admin, When I create a School Run Shield default policy, Then I can define standard windows per weekday per region, set buffer recommendations (min, max, default), and select enforcement level (Block or Warn). Given I save the default policy, When I reopen the Policies page, Then the policy persists and is applied by default to newly created teams. Given a region template exists, When I assign it to Region X, Then it applies to all teams tagged to Region X unless an explicit team-level override is set. Given overlapping or invalid windows in a template, When I attempt to save, Then the system prevents save and displays field-level errors indicating the conflicts in the region’s local time. Given a holiday calendar is associated to a region template, When the template is activated, Then protected dates are marked as unavailable to the scheduler API for that region.
Role-Based Visibility and Edit Controls
Given role mappings are configured (Org Admin, Region Owner, Team Lead, Member), When a user opens the Policies area, Then permissions are enforced as: Org Admin = view/edit all; Region Owner = view/edit within assigned regions; Team Lead = view presence and request exceptions; Member = view presence only. Given a visibility rule of Presence Only is enabled, When a non-admin views protected windows, Then exact times and labels are hidden and replaced with a generic reason (e.g., "Family block"). Given Presence Only is enabled, When ICS feeds, exports, or webhooks are generated for non-admin scopes, Then protected window times are redacted and generic labels are used. Given a user without edit permission attempts to modify or export policies, When the action is submitted, Then the system returns 403/permission error and records an audit event.
Enforcement Levels and Overrides
Given enforcement is set to Block for Region X, When a user proposes a meeting that overlaps a protected window in Region X, Then the system blocks scheduling and offers at least three alternate slots that preserve all participants’ work hours. Given enforcement is set to Warn, When a user proposes an overlapping slot, Then a non-dismissable warning is shown and the meeting can proceed only after explicit acknowledgement by the proposer. Given override permissions are granted to Org Admins (and optionally Region Owners), When an overlapping slot is attempted under Block, Then an override control is available to authorized users requiring an override reason before proceeding. Given an override is executed, When the meeting is scheduled, Then an audit log entry is created with override reason and affected policy, and the policy owner is notified.
One-Off Exception Approval Workflow
Given a Team Lead needs a single-occurrence exception, When they submit an exception request specifying date, start, end, and reason, Then approvers defined on the policy are notified and the request status is visible to the requester. Given an exception request is approved, When the meeting is scheduled, Then only the specified occurrence is permitted and the recurring series remains unchanged. Given an exception request is rejected or expires after the configured SLA, When the requester revisits the slot, Then scheduling is blocked/warned per policy and suggested alternates are presented. Given any decision on an exception, When the workflow completes, Then an audit entry is recorded with requester, approver, timestamps, outcome, and reason, with generic labels shown to non-privileged viewers. Given no approver group is configured, When an exception is submitted, Then the request routes to Org Admins by default; if no Org Admin exists, submission is blocked with guidance.
Import and Export of Policy Templates
Given I am an Org Admin, When I export policy templates in JSON or CSV, Then the download includes template names, regions, windows, buffers, enforcement levels, version numbers, and visibility rules, with no personal data or exact individual schedules. Given I import a valid template file, When I run a Dry Run, Then the system validates schema and references (regions, SCIM groups) and shows a change summary without applying changes. Given I import a valid template file and confirm Apply, When processing completes, Then templates are created/updated atomically, versions increment, and a success summary is shown. Given the import file contains schema or reference errors, When processing runs, Then no changes are applied and the error report lists row and field details. Given duplicate template names exist in the file, When importing, Then the system either blocks with actionable errors or auto-suffixes per configured naming policy and reports the resolution.
Change Notifications to Impacted Users
Given a policy change alters protected windows for a region or team, When the change is saved, Then impacted Org Admins, Region Owners, and Team Leads receive an in-app notification immediately and an email within 10 minutes, respecting notification preferences and quiet hours. Given multiple policy changes occur within 30 minutes, When notifications are sent, Then they are deduplicated into a single summary per audience. Given visibility is set to Presence Only, When notifications are delivered to non-admins, Then exact times and labels are redacted and replaced with generic language. Given webhooks are configured, When a policy changes, Then a policy.changed event is emitted with payload fields constrained to the subscriber’s role scope and visibility rules.
Audit and Compliance Logs with SSO/SCIM Integration
Given any create, update, delete, import, export, override, or exception decision, When the action occurs, Then an immutable audit event is recorded with actor (IdP user and SCIM group IDs), timestamp (UTC), IP, object, action, and before/after deltas. Given SCIM group membership changes in the IdP, When the next sync completes, Then role-based permissions in Timeglue reflect the new groups and subsequent audit events reference the updated group IDs. Given an auditor role queries logs, When a date range and filters are applied, Then results can be exported in CSV or JSON and include only data permitted by visibility rules (redacting protected details for non-privileged scopes). Given an attempt is made to alter or delete audit logs via UI or API, When the request is processed, Then the operation is rejected, an alert is raised, and a tamper-attempt audit event is recorded.
Calendar Sync and Holiday Awareness
"As a user, I want Timeglue to respect school events and local holidays from my calendar so that protection stays accurate without manual updates."
Description

Integrate with Google and Microsoft calendars to optionally ingest school/family events and region holiday calendars that may affect school‑run timing. Uses least‑privilege, read‑only scopes and webhooks/polling for reliable sync. Users can map specific calendars as "School" and configure rules (e.g., suspend morning drop‑off protection on school‑closure days or extend buffers during known events). Public holidays are resolved by participant region and applied alongside work hours. The system reconciles conflicts between imported events and recurring policies, surfaces discrepancies for review, and gracefully degrades during sync outages with clear status indicators.

Acceptance Criteria
Google Calendar Read‑Only Sync (Webhooks + Polling Fallback)
Given a user connects a Google account, When OAuth consent is granted, Then only read‑only, least‑privilege calendar scopes are requested and the app cannot create/update/delete events. Given initial connection completes, When sync runs, Then events within the configured sync horizon (default 30 days back, 90 days forward) from mapped calendars are ingested within 2 minutes without duplicates. Given a change is made on a mapped Google calendar, When a webhook notification is received, Then the change is reflected in Timeglue within 60 seconds. Given webhooks are unavailable or fail, When polling is active, Then changes are discovered and applied within 15 minutes. Given the user disconnects or revokes access, When sync is attempted, Then no further data is fetched and stored credentials are deleted within 60 seconds.
Microsoft 365 Calendar Read‑Only Sync (Subscriptions + Polling Fallback)
Given a user connects a Microsoft 365 account, When consent is granted, Then only read‑only, least‑privilege calendar permissions are requested and the app cannot create/update/delete events. Given initial connection completes, When the first sync runs, Then events within the configured sync horizon (default 30 days back, 90 days forward) from mapped calendars are ingested within 2 minutes without duplicates. Given a change occurs on a mapped Microsoft calendar, When a subscription notification is received, Then the change is reflected in Timeglue within 60 seconds. Given subscriptions lapse or fail, When polling is active, Then changes are discovered and applied within 15 minutes. Given the user disconnects or revokes access, When sync is attempted, Then no further data is fetched and stored credentials are deleted within 60 seconds.
Map Calendars as “School” and Configure School‑Run Rules
Given the user opens Calendar Mapping, When one or more calendars are marked as "School", Then they appear under School Sources and are used for rule evaluation. Given the rule "Suspend morning drop‑off on school‑closure days" is enabled, When a School‑mapped calendar has an all‑day event matching configured closure keywords/categories on a weekday, Then morning drop‑off protection is suspended for that day. Given the rule "Extend buffers during known events" is enabled, When a School‑mapped calendar has an event matching configured categories/keywords within the morning or afternoon window, Then the corresponding buffer is extended by the configured minutes for that day. Given rules are saved, When the schedule is recomputed, Then proposed meeting windows respect these School rules and a rule audit shows which rule affected each day.
Region Holiday Resolution and Application to Scheduling
Given each participant has a configured region, When schedules are generated, Then public holidays for each region are resolved and applied alongside work hours for the scheduling horizon. Given a holiday occurs in Region A but not Region B, When computing cross‑region availability, Then participants in Region A are treated as unavailable per configuration while Region B remains unaffected. Given the option "Suspend morning drop‑off on public holidays" is enabled, When a public holiday occurs in a participant’s region, Then morning drop‑off protection is suspended for that region/day. Given holiday data changes at the provider, When the next daily refresh runs, Then updated holidays are reflected within 24 hours.
Conflict Reconciliation and Discrepancy Review
Given a recurring School Run policy exists and an imported School event overlaps or contradicts it, When conflict detection runs after sync, Then the conflict appears in the Discrepancy Review panel with date/time, source, and recommended resolution within 60 seconds. Given a default precedence (Imported Events override Policies or Policies override Imported Events) is configured, When no manual action is taken, Then the default precedence is applied and logged for auditing. Given the user selects a resolution (Override by Event, Keep Policy, Create One‑Off Exception), When the action is confirmed, Then buffers and proposed meeting windows update immediately and the conflict is marked Resolved. Given multiple conflicts match a filter, When "Resolve All by Rule" is executed, Then all matching conflicts are resolved consistently and a summary result is shown.
Graceful Degradation and Clear Sync Status Indicators
Given a provider outage, token expiration, webhook deactivation, or polling failure is detected, When the condition occurs, Then the Sync Status shows Degraded with cause, affected account, last successful sync timestamp, and next retry time within 30 seconds. Given the system is Degraded, When meeting suggestions are shown, Then a "Sync outdated" badge appears if last successful sync is older than 30 minutes. Given recovery occurs, When sync succeeds, Then status returns to Healthy, backlog changes are applied, and "Sync outdated" badges are cleared on next refresh. Given repeated failures, When retries are scheduled, Then exponential backoff is applied up to 15 minutes between attempts and is visible in the status panel.
One‑Off Exceptions for Special Events Without Altering Series
Given a recurring School Run buffer series exists, When the user creates a one‑off exception for a specific date due to a special event, Then only that date’s buffers are modified and the rest of the series remains unchanged. Given an exception is set to suspend or extend buffers by configured minutes, When proposed meeting times are generated for that date, Then they respect the exception. Given the imported special event is cancelled or moved, When sync detects the change, Then the user is prompted to update or remove the linked exception and the exception is flagged for review until resolved.

Siesta Sense

Understands midday rest patterns where split shifts are common and marks those periods as fragile by region. Suggests morning or late‑afternoon overlaps or compressed sessions instead of intruding on rest hours. Helps global teams avoid low‑energy time slots and improves attendance and engagement.

Requirements

Regional Siesta Pattern Knowledgebase
"As a remote team lead scheduling across regions, I want the system to know local siesta times by region and industry so that it avoids proposing meetings during culturally sensitive rest periods."
Description

A maintained, versioned dataset of midday rest patterns by country, region, and city (with industry-specific variations), encoding typical start/end windows, variability ranges, seasonal shifts, and a fragility score. Includes data lineage, confidence levels, and fallback rules when a region is unknown. Provides localization, caching, and a scheduled update pipeline that ingests trusted public labor sources and curated user feedback. Exposes a query interface for the scheduling engine to retrieve applicable siesta windows per participant.

Acceptance Criteria
City Query with Industry Override and Seasonal Application
Given dataset version V1 contains records for ES/Catalonia/Barcelona with a city default and an industry="hospitality" override and a seasonal shift for June–August When the query interface is called for city=Barcelona, country=ES, industry=hospitality, date=2025-08-15 Then the selected record origin_level=industry_city and seasonal_shift_applied=true And the response includes windows[] with at least 1 item, each having start_local and end_local as RFC3339 timestamps with IANA timezone "Europe/Madrid" And variability_minutes_range has min>=0 and max>=min and both are integers And fragility_score is present and 0.0<=fragility_score<=1.0 with precision to 2 decimals And confidence is present and 0.0<=confidence<=1.0 with precision to 2 decimals And dataset_version="V1" is included in the response
Unknown Region Fallback Resolution
Given the dataset has a country-level record for ES but no region or city record for Fictionville, ES When the query interface is called for city=Fictionville, country=ES, industry=software, date=2025-10-10 Then the response uses fallback precedence industry_city > industry_region > city > region > country > global_default and selects origin_level=country And fallback_chain is returned and shows attempted levels before selection And windows[] is present with valid RFC3339 local timestamps and IANA timezone for the country default And confidence equals the selected record’s confidence And fallback_reason is provided and non-empty
Data Lineage and Confidence Integrity
Given any response from the query interface When inspecting metadata Then lineage.sources has length>=1 and each source has name, type in {public_labor,gov,academic,user_feedback}, url (valid URL), and retrieved_at (RFC3339) And if user_feedback contributed to the record, lineage.moderated=true and lineage.user_feedback_count>=1 And confidence is present, numeric, and 0.0<=confidence<=1.0 with precision to 2 decimals
Versioning and Immutability
Given dataset version V1 is published and later V2 is published When the query interface is called with explicit version=V1 Then dataset_version in the response equals "V1" and the record checksum matches the V1 snapshot checksum When the query interface is called with no version parameter Then dataset_version equals the latest published version ("V2") And dataset versions follow semantic versioning matching ^\d+\.\d+\.\d+$ And ETag headers are stable per version; a repeated GET with If-None-Match for a stored ETag of V1 returns 304 if unchanged
Localization and Timezone Normalization
Given Accept-Language=es-ES and participant timezone Europe/Madrid When the query interface is called Then display_name and region labels are localized to es-ES And time fields are returned as RFC3339 timestamps localized to Europe/Madrid with the correct DST offset for the given date When Accept-Language is an unsupported locale (e.g., xx-XX) Then the API falls back to en-US for labels And ISO formats (RFC3339 timestamps, numeric fields) are not localized
Query Interface Caching and Performance SLAs
Given a warm cache and steady state When executing 100 single-participant queries for the same city/industry across 1 minute Then p95 latency<=100ms and cache_hit_ratio>=0.90 Given a cold cache When executing 100 single-participant queries Then p95 latency<=500ms And responses include Cache-Control: public, max-age=86400 When a new dataset version is published Then cached entries for prior version are invalidated or refreshed within 5 minutes And cache keys include dataset_version, locale, location granularity, industry, and date
Scheduled Update Pipeline and Data Quality Gates
Given a daily schedule at 02:00 UTC and configured trusted sources plus curated user feedback When the pipeline runs Then raw ingests from each source pass schema validation (100% required fields present) or the run fails closed with an alert emitted And a candidate dataset is produced with validation checks passing for: required fields (windows, variability range, seasonal shifts if applicable, fragility_score, confidence, lineage), valid time formats, value ranges, and referential integrity for country/region/city codes And a new semantic version is created only if there is any data change; otherwise the previous version remains current And publication of a new version requires manual approval or automated gate pass and writes an audit log with version, source list, record counts, and changeset summary And failed runs do not alter the current published version
Siesta-Aware Scheduling Engine
"As a meeting organizer, I want the scheduler to automatically avoid fragile midday rest times and prefer morning/late‑afternoon overlaps so that invitations respect participants and improve attendance."
Description

Extend Timeglue’s constraint solver to treat siesta windows as soft constraints with tunable thresholds. Combine work hours, holidays, focus blocks, and siesta fragility to compute a ranked set of candidate meeting times. Bias scoring toward morning and late‑afternoon overlaps and propose compressed sessions when overlap is limited. Provide deterministic behavior with clear conflict reasoning and deterministic tie‑break rules. Configurable at workspace, team, and event levels.

Acceptance Criteria
Soft siesta constraints with thresholded inclusion
Given a workspace siesta_violation_threshold of 0.70 and region ES-Madrid siesta window 13:30–16:00 local with fragility=0.80 And an event requiring 60 minutes with participants in Madrid and New York with work hours, holidays, and focus blocks defined When the engine computes candidate times for the next 14 days Then any candidate whose siesta_violation_score for any impacted participant > 0.70 is excluded from the results And all included candidates have siesta_violation_score <= 0.70 for all impacted participants And each included candidate contains reasoning.siesta with overlap_minutes, fragility, and computed siesta_violation_score And no candidate violates hard constraints (outside work hours, holidays, or focus blocks) for any participant
Morning and late‑afternoon bias in ranking
Given morning window 08:00–11:00 local and late‑afternoon window 16:00–19:00 local are configured at the workspace level And candidates A and B are both feasible (no hard‑constraint violations) and have equal total overlap and equal siesta_violation_score And candidate A’s local times for ≥50% of participants fall mostly between 12:00–15:00 (midday) And candidate B’s local times for ≥50% of participants fall mostly within the morning or late‑afternoon windows When the engine ranks candidates Then candidate B ranks higher than candidate A And candidate B’s reasoning.bias includes morning_afternoon_bonus > 0 while candidate A’s is 0 And if both have equal bonus and equal total score, the earlier UTC start ranks higher
Compressed session proposals when overlap is limited
Given an event duration of 90 minutes with allow_compressed=true at the event level And no continuous overlap ≥90 minutes exists within thresholds for all required participants over the next 7 days And two non‑overlapping segments of 45 minutes each exist within morning/late‑afternoon windows that satisfy thresholds When the engine computes candidates Then at least one candidate of type "compressed_session" is returned with exactly two segments totaling 90 minutes And both segments individually satisfy hard constraints and have siesta_violation_score <= the effective threshold for all impacted participants And the candidate includes reasoning.compressed with reason="insufficient_continuous_overlap" and segment details (start, end, timezone) And if allow_compressed=false, no compressed_session candidates are returned
Deterministic ranking and tie‑break rules
Given a fixed input set (participants, calendars, regions, configuration) and deterministic execution (no random seed) When the engine is run twice to compute candidates Then the ordered list of candidates (ids and scores) is identical across runs And when two candidates have equal total_score, the one with earlier UTC start ranks higher And when UTC start is equal, the one with lower total_soft_penalty ranks higher And when still tied, the one with lexicographically smaller candidate_id ranks higher
Conflict reasoning and penalty breakdown in API response
Given an API request with include_reasoning=true When the engine returns candidates and excluded windows Then each candidate includes reasoning with: per_participant.overlap_minutes, per_region.siesta_overlap_minutes, penalty_components {siesta, focus, outside_work_hours, holiday}, bonuses {morning_afternoon_bias}, total_soft_penalty, and total_score And each excluded window includes at least one reason_code (e.g., holiday, focus_block, siesta_above_threshold) and the blocking participant/region And for each candidate, total_score = base_score − sum(penalty_components) + sum(bonuses) within ±0.01 tolerance And numeric values and reason codes are deterministic across identical runs
Configuration precedence across workspace, team, and event levels
Given workspace defaults (siesta_violation_threshold=0.70, morning window 08:00–11:00) And a team override (siesta_violation_threshold=0.60) And an event override (siesta_violation_threshold=0.50, morning window 09:00–11:00) And participants assigned to that team When the engine computes candidates for the event Then the effective siesta_violation_threshold is 0.50 and the effective morning window is 09:00–11:00 And after removing the event override, the effective threshold becomes 0.60 and morning window reverts to 08:00–11:00 And for participants not on the team, workspace defaults apply unless an event‑level override is present
Fragility Visualization on Timeline
"As a scheduler, I want to see at a glance which time slots are fragile due to local rest patterns so that I can choose respectful windows quickly."
Description

Add a visual overlay to the draggable timeline showing siesta windows with distinct color/pattern and a legend. Tooltips reveal local time, fragility score, rationale, and suggested alternative windows. Supports zoom levels, responsiveness, dark mode, and keyboard/screen‑reader accessibility (ARIA labels and high‑contrast patterns). Users can toggle the overlay per invitee or aggregate view and export a screenshot for async discussion.

Acceptance Criteria
Visualize siesta fragility overlays with legend
Given a timeline with at least 2 invitees who have siesta windows configured When the Fragility overlay is enabled Then siesta windows render as semi‑transparent overlays with distinct color and hatch pattern per invitee And overlapping windows are visually distinguishable without obscuring time/grid labels And a legend appears mapping color/pattern to invitee and fragility level And the first paint of overlays completes within 300 ms for up to 15 invitees on a 1‑week view And toggling the overlay OFF removes all overlays and the legend within 150 ms
Show tooltip details for fragile windows
Given a fragile window overlay segment is hovered by mouse or focused via keyboard When the interaction persists for at least 150 ms Then a tooltip appears anchored to the segment within 200 ms And the tooltip displays: local time range (with TZ abbreviation and UTC offset), fragility score (0–100), rationale (≤140 chars), and 2 suggested alternative windows (start–end, local) And times reflect the invitee’s DST rules on the displayed date And pressing Esc or moving focus hides the tooltip within 100 ms And tooltips remain fully within the viewport by flipping/shifting as needed
Maintain legibility across zoom levels and responsive breakpoints
Given the user adjusts timeline zoom between 50% and 200% or resizes the viewport to 320, 768, 1024, or 1440 px widths When overlays are rendered Then hatch pattern unit size remains between 4 px and 12 px and never collapses to a solid fill And time labels and overlay edges remain aligned to the grid at all zoom levels And the legend scales to remain readable; at ≤768 px it collapses to an icon button that expands on tap/click And the legend does not introduce horizontal overflow at ≤320 px; the timeline maintains intended scroll behavior only
Dark mode and high‑contrast compliant overlay rendering
Given the app is in dark mode or the system prefers a dark color scheme When overlays and the legend are displayed Then overlay foregrounds for text meet ≥4.5:1 contrast against the background and non‑text graphics meet ≥3:1 And patterns are used in addition to color to differentiate states for users with color‑vision deficiencies And the specified contrast ratios are preserved at all supported zoom levels
Keyboard and screen‑reader accessible overlays and legend
Given a keyboard‑only or screen‑reader user navigates the timeline When focus moves to a fragile window overlay Then the element is reachable via Tab/Shift+Tab in chronological left‑to‑right order And it exposes an accessible name and description (ARIA) including invitee/region, local time range, fragility score, and rationale summary And activating Enter or Space opens the tooltip which is announced via aria‑describedby or aria‑live without trapping focus And the overlay toggle and legend are operable via keyboard and expose name/role/value with aria‑pressed reflecting state And interactions conform to WCAG 2.1 AA criteria 2.1.1, 2.4.3, and 4.1.2
Per‑invitee and aggregate overlay toggling
Given a meeting with three or more invitees When the user toggles overlays per invitee via the legend or participant list Then only the selected invitees’ overlays appear/disappear without page reload and within 150 ms And the Aggregate view toggle produces a combined overlay representing the union of all selected invitees’ siesta windows with intensity indicating overlap count And the legend updates to include an Aggregate entry with an intensity scale And selected toggle states persist for the current meeting draft during the session
Export screenshot for async discussion
Given the user clicks Export Screenshot on the timeline When the export completes Then a PNG at 2× device pixel ratio is downloaded within 2 seconds for a 1‑week view with up to 15 invitees And the image includes the visible timeline area, all active overlays, the legend, meeting title, current date/time, and active time zone And tooltips are excluded unless explicitly open on export; open tooltips render fully within bounds And the exported image matches the current theme (light/dark) and zoom scale; all text is readable at ≥12 px effective size And the filename follows meetingname_YYYYMMDD_HHMM_TZ_timeglue.png
Participant Region Detection & Privacy Controls
"As a privacy‑conscious user, I want control over how my region is detected and used so that siesta‑aware scheduling respects my privacy and legal requirements."
Description

Infer participant region/time zone from calendar metadata and user profile with explicit consent, and allow manual selection when inference is uncertain. Store only minimal region granularity required for scheduling, with clear settings to opt out or adjust precision. Provide compliant data handling (GDPR/CCPA), retention policies, consent logs, and a privacy-safe mode that uses generic regional defaults without personal data.

Acceptance Criteria
Explicit Consent Prompt for Region Inference
Given a first-time participant with no prior consent, When region inference is about to run, Then a consent dialog is shown before any inference executes. Given the participant accepts, Then inference runs and consent is stored with timestamp (UTC), policy version, purpose, and data sources. Given the participant declines, Then no inference runs, no region data is stored, and privacy-safe mode is enabled for scheduling. Given the consent dialog, Then it includes purpose, data sources, retention period, and links to Privacy Policy and DPA, with Accept and Decline options.
Manual Region Selection on Low Confidence
Given inference confidence is below 0.7 (configurable), When scheduling requires a region to proceed, Then the participant is prompted to select a region/time zone manually. Given the participant selects a region, Then the manual selection overrides inference and is marked as user-provided with timestamp. Given privacy precision is set (e.g., time zone only), Then the manual selection options are constrained to the allowed granularity. Given the participant skips selection, Then privacy-safe defaults are applied and the UI indicates reduced accuracy.
Minimal Region Granularity Storage
Given org precision is set to 'time zone', When storing participant location, Then only the IANA time zone (e.g., Europe/Madrid) is persisted; no city, coordinates, or raw offsets are stored. Given org precision is lowered (e.g., from city to region), Then higher-precision historical records are truncated or deleted within 24 hours and cannot be restored. Given data export is requested, Then only the stored granularity is included, with no hidden or derived higher-precision fields. Given API responses include region data, Then they include a precision field and omit disallowed fields per current settings.
Opt-Out and Precision Controls
Given a participant toggles 'Use my data for region detection' off, Then inference stops immediately and existing region data is deleted or anonymized within 24 hours, with confirmation to the user. Given the precision control is changed, Then future scheduling uses the new precision and the change is logged with actor, timestamp, and previous value. Given opt-out is enabled, Then invitations and shared links use generic regional defaults and exclude personal region information. Given privacy-safe mode is active, Then public APIs return privacy_mode=true and exclude personal region fields.
Consent Logging and Data Retention Compliance
Given consent is granted, withdrawn, or updated, Then a consent log entry is recorded with subject ID/pseudonym, action, timestamp (UTC), policy version, and lawful basis. Given a GDPR/CCPA access or deletion request, When verified, Then all region data and related consent logs are exported or deleted within 30 days and the request is auditable end-to-end. Given a retention policy of 12 months (configurable), Then region data older than the policy is automatically purged daily and recorded in audit logs. Given an administrator audits access, Then the system can report who accessed region data in the past 90 days with purpose and timestamp.
Privacy-Safe Mode Scheduling Defaults
Given privacy-safe mode for a participant, When Siesta Sense proposes overlaps, Then it uses regional defaults (e.g., country-level siesta windows) without storing or transmitting personal identifiers. Given privacy-safe mode, Then suggested slots are labeled as 'region default-based' and avoid all marked fragile periods for each region involved. Given privacy-safe mode, When suggestions are computed server-side, Then inputs are anonymized (region only), are not persisted, and are deleted immediately after computation. Given conflicting regional defaults across participants, Then the algorithm selects overlaps that avoid all fragile windows and surfaces trade-offs without logging personal data.
Override & Consent Workflow
"As an organizer, I want a clear way to override a fragile slot with documented consent so that urgent meetings can proceed without surprising participants."
Description

Enable organizers and invitees to override fragile siesta slots with explicit confirmation, reason capture, and scope (single event or series). Notify affected participants, record consent in event metadata, and display conflict badges in the UI. Provide workspace policies to prevent overrides, require majority consent, or limit overrides to admins. Include an audit trail and API support for compliance reporting.

Acceptance Criteria
Organizer Override of Fragile Siesta Slot (Single vs Series)
Given an organizer attempts to schedule a meeting that overlaps a fragile siesta slot for at least one participant's region and workspace policy allows overrides When the organizer initiates scheduling in that slot Then an Override dialog is shown requiring: explicit confirmation checkbox, a reason text field (10–250 characters), and a scope selector with options "This event" (default) and "Entire series" (if recurring) And the Confirm Override action remains disabled until all required inputs are valid When the organizer confirms the override Then the event is created with override_state = "Pending Consent" if any consent is required by policy, otherwise override_state = "Confirmed" And if the scope is "Entire series", the override is applied to all future occurrences that overlap fragile siesta slots within the series
Invitee Consent Capture and Change
Given an invitee is required to consent to an override for a meeting that affects their fragile siesta slot When the invitee views the event in-app or via the email link Then they are presented with Accept and Decline actions, the organizer's reason, the scope, and the consent deadline When the invitee accepts Then their consent = true is recorded with timestamp, actor_id, and channel, and the consent tally updates in real time When the invitee declines Then their consent = false is recorded and the event shows a conflict state for that invitee And the invitee can change their decision until the consent deadline, with each change audit-logged
Notifications for Override Initiation and Outcomes
Given an override is initiated, updated, or reaches a decision outcome When the action occurs Then notifications are sent to all affected participants within 60 seconds via configured channels (email and in-app) including: event title, local date/time per recipient, fragile slot impacted, reason summary, scope, required action (if any), and consent deadline And the organizer receives a summary message with current consent tally and policy rule applied
Event Metadata and UI Conflict Badges
Given an event has an override When any participant views it on the timeline or event details Then a "Fragile Override" badge is displayed with state labels: Pending, Confirmed, Blocked, or Failed, and a tooltip reveals the reason and scope And the event metadata includes override fields: initiator_id, reason, scope (single|series), policy_id, required_majority, consent_deadline, per_participant_consents[], created_at, updated_at And this metadata is retrievable via the public API for the event with authorized access
Workspace Policy: No Overrides and Admin-Only Overrides
Given the workspace policy is set to "No Overrides" When any user attempts to schedule in a fragile siesta slot Then scheduling is blocked and the user is prompted to pick an alternative suggested window, with no option to override Given the workspace policy is set to "Admin-Only Overrides" When a non-admin attempts to override Then the Confirm Override action is disabled with an explanatory message and a link to request admin assistance When an admin attempts to override Then the override flow proceeds as normal
Workspace Policy: Majority Consent Threshold and Deadline
Given the workspace policy requires majority consent with threshold T% among required invitees and a consent deadline of D hours When an override is initiated Then the system calculates the number of required accepts = ceil(T% of required invitees) and sets a consent deadline at now + D And the event remains in override_state = "Pending Consent" until the threshold is met before the deadline When the threshold is met before the deadline Then the event moves to override_state = "Confirmed" and participants are notified When the deadline passes without reaching the threshold Then the override_state = "Failed" and the organizer is prompted with alternatives (morning/late-afternoon overlaps or reschedule suggestions), and participants are notified
Audit Trail and Compliance Reporting API
Given any override-related action occurs (initiate, confirm, accept, decline, revoke, fail, policy change) When the action is saved Then an immutable audit record is written containing: event_id, actor_id, action, timestamp (UTC), request_id, ip_hash, policy_snapshot, previous_value, new_value And audit records are retained for at least 365 days and are filterable by time range, user, event, action When an authorized admin calls the Compliance API with a valid token and filters Then the API returns paginated JSON within 2 seconds for up to 10,000 records with fields sufficient to reconstruct consent history and outcomes
Outcome Feedback Loop
"As a team lead, I want feedback on meeting outcomes by time slot so that Siesta Sense improves recommendations over time."
Description

Collect optional post‑meeting signals (attendance, declines, no‑shows, quick emoji energy ratings) to assess slot quality. Aggregate and anonymize metrics to refine fragility scores and suggestion bias per region/team over time. Provide dashboards with KPIs (attendance delta, average energy rating by slot, override frequency) and a feedback API. Opt‑in controls and data deletion tools included.

Acceptance Criteria
Post-Meeting Signal Collection (Attendance & Energy Emoji)
Given a meeting created via Timeglue and at least one participant with feedback opt-in enabled When the meeting ends Then eligible participants receive a prompt within 2 minutes to submit attendance (attended/declined/no-show) and a 5-emoji energy rating Given a participant opens the unique feedback link within 24 hours When they submit their signals Then the submission is accepted once per participant per meeting and stored with timestamp and locale Given no submission occurs within 24 hours When the reminder policy is evaluated Then exactly one reminder is sent and the link expires after 24 hours Given participants without opt-in or in excluded jurisdictions When the prompt scheduler runs Then no prompts are sent and no signals are collected
Aggregation & Anonymization Safeguards
Given collected signals exist When generating aggregates for dashboards and models Then all PII is removed and results meet k-anonymity >= 5 per group; groups below threshold are suppressed Given aggregates are produced When grouping by team, region, slot-hour, and ISO week Then each aggregate includes sample_size and a k_threshold_applied flag Given a user or org opts out When nightly aggregation runs Then their signals are excluded from new aggregates within 24 hours Given UI and API access When requesting data Then no individual-level signal is retrievable
Slot Quality KPIs Dashboard
Given an org with aggregated data and a user with Analytics Viewer role When the user opens the Siesta Sense dashboard Then KPIs are displayed: attendance delta vs 8-week baseline, average energy rating by slot, no-show rate, decline rate, override frequency, with sample sizes Given filters (date range, team, region, meeting type) When any filter is applied Then results update and server responds within 2 seconds and KPIs recompute accordingly Given new signals are submitted When data pipelines complete Then dashboard data reflect changes within 15 minutes Given the user exports data When CSV or JSON is requested Then the export includes applied filters, generated-at timestamp, and matches on-screen aggregates
Feedback API (Submit & Retrieve Aggregates)
Given a client authenticated via OAuth2 client credentials with scopes 'signals:write' and/or 'signals:read:agg' When POST /v1/feedback/signals is called with a valid payload (meeting_id, participant_pseudonym, attendance_status, energy_rating 1-5, timestamp, idempotency-key) Then return 201 Created with signal_id; duplicate idempotency-key returns 200 with the original resource Given authorized read access When GET /v1/feedback/aggregates is called with valid filters (org_id, team_id, region, date_range, slot_hour) Then return 200 with anonymized aggregates including sample_size and k_threshold_applied flag Given high request volume When >600 requests/minute per org is exceeded Then return 429 Too Many Requests with a Retry-After header Given invalid schema or insufficient scope When requests are made Then return 400 for validation errors and 403 for authorization errors with machine-readable error codes
Fragility Score & Suggestion Bias Refinement Loop
Given rolling 4-week aggregates per region/team/slot-hour with sample_size >= 50 When attendance delta or average energy rating deviates from baseline by ≥5% with p<0.05 Then adjust the fragility score for the slot by up to ±10% per cycle and update suggestion bias weights accordingly Given an adjustment is applied When logging the change Then record before/after values, metric deltas, rationale, timestamp, and job run id in an audit log Given a 10% holdout using current weights When the treatment group's attendance drops by >5% or override frequency increases by >5% versus holdout over 2 consecutive runs Then auto-revert to previous weights and notify owners via email and webhook Given nightly processing When the refinement job runs Then execute at 02:00 local time for each org window without overlapping runs
Opt-In, Consent, and Data Deletion Controls
Given org-level feedback collection is OFF by default When an org admin enables it and configures consent text Then collection begins only for teams and users who also opt-in Given a user revokes consent When consent status changes Then no further prompts are sent and their historical signals are queued for deletion Given an admin requests deletion for a user/team/date-range When the deletion job runs Then raw signals are purged within 72 hours and aggregates/models are rebuilt excluding them within the same window Given a deletion is executed When audit logging occurs Then an audit record captures actor, scope, timestamp, and count of records affected, and a pre-deletion export is available to the admin Given data retention policies When retention windows elapse Then raw signals are retained 12 months and aggregates 24 months, after which data are automatically purged
Smart Link Enhancements for Siesta
"As an external client booking with my team, I want booking links that avoid low‑energy midday windows so that I can select mutually convenient times without back‑and‑forth."
Description

Augment Timeglue smart links to hide fragile siesta times by default and annotate visible windows with brief explanations. Include parameters to enforce morning/late‑afternoon bias, show/hide fragile times, and request consent if a fragile slot is chosen. Ensure ICS and external calendar compatibility and expose siesta‑aware availability via API for third‑party booking tools.

Acceptance Criteria
Hide Fragile Siesta Times by Default in Smart Links
Given a smart link with participants across regions that have defined siesta periods And no explicit fragile parameter is set When an invitee opens the link timeline Then time slots overlapping any participant’s fragile siesta periods are hidden And visible windows display a brief suitability annotation of 80 characters or fewer And a legend indicates "Fragile hours hidden" is present on the page And annotations are available via tooltip with accessible aria-labels and keyboard focus And behavior is consistent on desktop and mobile viewports (≥320px width)
Smart Link Parameter to Show/Hide Fragile Times
Given a smart link includes fragile=show When the timeline renders Then fragile slots are displayed with a distinct "Fragile" badge and dashed shading And fragile slots are not auto-selected by any auto-suggestion logic Given a smart link includes fragile=hide When the timeline renders Then fragile slots are not displayed Given a smart link includes an invalid fragile value When the timeline renders Then the system defaults to hide and logs a non-blocking client warning without exposing fragile slots And the fragile parameter value is preserved when the link is reshared or an ICS is generated
Morning/Late-Afternoon Bias Enforcement
Given a smart link includes bias=morning When suggested windows are computed Then for each suggested window at least 80% of participants experience local time within [workday_start, siesta_start) and no participant’s time overlaps siesta or non-work hours Given a smart link includes bias=late_afternoon When suggested windows are computed Then for each suggested window at least 80% of participants experience local time within [siesta_end, workday_end] and no participant’s time overlaps siesta or non-work hours Given bias=none (default) When suggested windows are computed Then windows use standard overlap rules without time-of-day bias Given no feasible window satisfies the selected bias When computation completes Then the UI shows "No windows match bias" and offers a one-click option to relax bias to none
Consent Workflow When Fragile Slot Is Chosen
Given a smart link includes fragile=show and consent=required When an invitee selects a fragile slot Then a modal explains the fragile period context with region-specific note and requires checking "I consent to meet during a fragile period" before enabling Confirm And if consent is not given Then the booking cannot be created and selection remains pending And upon confirmation Then the booking is created, organizer notifications include a "Fragile slot—consented" tag, and the event description includes consent timestamp and region And consent details are stored and retrievable via API at GET /bookings/{id} with fields consented=true, consent_timestamp, consent_region
ICS and External Calendar Export Compatibility
Given a confirmed booking created via a Siesta-aware smart link When an ICS is generated and imported into Google Calendar, Outlook, and Apple Calendar Then DTSTART/DTEND include TZID with IANA zone and reflect identical local times across systems And DESCRIPTION includes visible window annotations and, if applicable, "[Fragile slot—consented]" And no participant receives an after-hours time due to timezone conversion And updates and cancellations preserve ICS UID for correct propagation And the ICS ATTENDEE/ORGANIZER fields and conferencing links remain intact after import
Siesta-Aware Availability API for Third-Party Tools
Given a valid API key and request to GET /v1/availability with participants, regions, fragile, and bias parameters When the request is processed Then the response is 200 with JSON items containing fields: start (ISO8601 with zone), end (ISO8601 with zone), isFragile (boolean), explanation (string ≤80 chars), participants_covered (0–100), bias_applied (string) And fragile=hide excludes fragile windows; fragile=show includes them with isFragile=true And unknown bias values default to none and return error_code=null And p95 latency ≤700ms for up to 10 participants and a 30-day range And responses include rate-limit headers and return 429 when limits are exceeded And responses include ETag; If-None-Match returns 304 when unchanged And invalid parameters return 400 with machine-readable error_code and message And the endpoint maintains v1 backward compatibility and rejects missing version with 404

Respect Index

Scores each proposed slot or series on cultural respect, factoring observances, siestas, family windows, and local customs. Shows what drives the score and offers one‑click improvements, so you can defend choices to stakeholders and pick the most considerate option. Pair with FairShare to balance both equity and respect across time zones.

Requirements

Cultural Context Data Layer
"As a remote team lead, I want the system to understand each participant’s local customs and observances so that proposed meeting times avoid culturally insensitive windows."
Description

Foundational data service that normalizes and maintains per-locale cultural context used by Respect Index. Aggregates public holidays, regional observances, siesta/midday breaks, weekly worship/prayer times, school-run/family windows, and local “no‑meeting” customs, mapped to time zones and participant locales. Supports recurring rules, exceptions, daylight saving changes, and per-user overrides. Includes update cadence (daily diff ingest), conflict resolution (company policy > user preference > public source), and graceful fallbacks when data is missing. Exposes a query API for the scoring engine with low-latency lookups and caches, and an admin UI to review/edit data and audit provenance. Ensures privacy by storing only normalized rule windows, not sensitive personal context, unless explicitly provided by the user.

Acceptance Criteria
Daily Diff Ingest and Normalization per Locale
- Given configured public sources and locales, When the scheduled ingest runs at 02:00 UTC daily, Then the system fetches only diffs since the last successful version and completes within 15 minutes. - Given ingested records, When normalization runs, Then rules are mapped to locale + IANA time zone windows with local start/end and UTC offsets, deduplicated, validated, and versioned. - Given a transient source failure, When retry policy triggers, Then it retries up to 3 times with exponential backoff and marks the source partial while preserving last-known-good data. - Given a successful ingest, When validation runs, Then ≥ 99% of records pass schema and provenance checks; failures are quarantined and alerts are emitted.
Conflict Resolution Precedence (Company > User > Public)
- Given overlapping rule windows from multiple sources, When a query resolves rules, Then the resulting window set reflects precedence Company > User > Public with deterministic tie-breaking by most recent version. - Given a company policy that forbids meetings during 12:00–14:00 local, When a user preference allows 13:00–14:00, Then the resolved window forbids 12:00–14:00. - Given a user override forbidding Friday after 15:00, When the public source indicates no restriction, Then Friday after 15:00 is excluded. - Given a company policy is rescinded and a new version is published, When queries are made, Then the change is reflected within 60 seconds.
Recurring Rules, Exceptions, and DST Transitions
- Given a weekly siesta rule 13:00–15:00 local Mon–Fri, When computing windows for any week, Then the rule recurs on the specified weekdays. - Given an exception date that overrides the recurring rule, When the date is in range, Then the exception supersedes the recurring rule for that date. - Given a DST transition day, When computing before and after the shift, Then windows align to local wall-clock time and UTC offsets adjust correctly with no unintended overlaps or gaps. - Given a lunar-calendar observance mapped to civil dates, When querying that month, Then the correct converted dates are used.
Low-Latency Query API and Caching
- Given a single-locale query for a 7-day horizon, When the API is called under normal load, Then p95 latency is ≤ 80 ms and p99 ≤ 150 ms. - Given repeated identical queries, When caching is enabled, Then cache hit ratio is ≥ 70% and responses include a version/ETag. - Given a new data version is promoted, When queries arrive, Then caches invalidate within 60 seconds and responses reflect the new version. - Given a bulk query of up to 50 participants/locales, When the API is called, Then p95 latency is ≤ 300 ms and partial failures are reported per item without failing the entire batch.
Graceful Fallbacks on Missing or Sparse Data
- Given a locale with no public source coverage, When a query is made, Then the API returns a neutral baseline of organization work hours only with fallback=true and provenance='fallback'. - Given a source outage, When using last-known-good data, Then the API serves the previous version with stale=true and includes source_staleness_age in seconds. - Given insufficient confidence below threshold, When conflicting data cannot be resolved, Then the API returns an explicit 'unknown' constraint type and excludes it from hard blocks.
Admin UI Review/Edit and Provenance Audit
- Given an admin with edit permissions, When they modify a locale’s siesta window, Then the change is validated, saved, and visible in the UI list within 5 seconds. - Given any create/update/delete via UI or API, When saved, Then an audit record is created with actor, timestamp, before/after diff, source, reason, and ticket reference fields. - Given an audit record, When an admin selects Revert, Then the prior version is restored and propagated to the query API within 60 seconds. - Given a non-admin, When accessing the admin UI, Then access is denied and no data is exposed.
Privacy Guardrails and Consent for Sensitive Context
- Given data ingestion from public sources, When persisting, Then only normalized rule windows and non-PII provenance are stored; free-text personal notes are rejected by schema. - Given a user explicitly provides a personal constraint, When saved, Then consent is captured (timestamp, scope, purpose) and the constraint is scoped to that user only. - Given a data export or audit, When inspecting stored records, Then no sensitive personal context is present unless explicitly provided with consent. - Given a user withdraws consent, When processed, Then personal constraints are deleted within 24 hours and removed from query results within 60 seconds.
Respect Score Engine
"As a scheduler, I want a single, transparent score for each proposed time so that I can quickly compare options and choose the most respectful slot."
Description

Deterministic, explainable scoring model that evaluates each candidate slot and recurring series on a 0–100 Respect Index. Factors include participant work hours, cultural context windows, individual preferences (e.g., child care), company policies, and sensitivity to late/early hours. Supports per-participant penalties/boosts, diminishing returns, and configurable weightings. Computes both per-slot and series-average scores with outlier detection (e.g., one participant severely impacted). Must operate within sub‑150ms per candidate slot at P95 with vectorized lookups from the context layer and handle incomplete data via conservative heuristics. Returns structured output: overall score, per-driver contributions, impacted participants, and improvement hints.

Acceptance Criteria
Per-Slot Score: Deterministic, Explainable, Structured Output
Given a fixed candidate slot and identical inputs, When the engine computes the respect score 100 times, Then the overall score and per-driver contributions are identical across runs. Given per-driver contributions are returned, When summed, Then the sum equals the overall score within ±0.5 points and rounding precision is specified in metadata.precision. Given a computed result, Then the output includes fields: overallScore (0–100), drivers[], impactedParticipants[], improvementHints[], and metadata{inputsHash,timestamp,engineVersion}. Given at least one driver has a non-zero penalty or boost, Then improvementHints contains at least one actionable hint referencing that driver with a one-click adjustment token. Given all drivers are neutral for all participants, Then overallScore = 100 and improvementHints is empty.
Performance and Vectorized Context Lookups (P95 ≤ 150ms)
Given a batch of 1,000 candidate slots across 10 participants, When scoring in a warm process, Then P95 per-slot latency ≤ 150 ms and P50 ≤ 60 ms measured over the batch. Given those 1,000 slots with shared participants, When fetching context, Then the context layer is invoked using batch calls: ≤ 1 call per context domain per participant (workHours, holidays, customs, preferences, policies), not per slot. Given a cold start, When the first batch is scored, Then P95 per-slot latency ≤ 250 ms for the first batch and subsequent batches meet the ≤ 150 ms target. Given metrics instrumentation is enabled, Then per-run latency distributions and context-call counts are emitted for validation.
Series Scoring with Outlier Detection
Given a recurring series of N candidate slots, When scored, Then series.overallScore equals the arithmetic mean of the per-slot overall scores rounded to the nearest 0.1. Given any participant whose median per-slot penalty exceeds the configured outlierPenaltyThreshold (default 25) or whose worst-slot penalty ≥ severePenaltyThreshold (default 35), Then outliers.participants includes that participant with evidence (slotIds and penalties). Given any slot where a participant penalty ≥ severePenaltyThreshold, Then outliers.slots includes that slot and impactedParticipants. Given no participant exceeds thresholds, Then outliers collections are empty.
Configurable Weightings, Adjustments, and Diminishing Returns
Given two weighting configurations W1 and W2 where W2 doubles the weight of earlyHours penalties, When scoring the same slot, Then the overall score under W2 decreases by the expected delta computed from driver contributions. Given a participant with a +10 boost for childCareWindow and a −15 penalty for lateNight, When applied, Then the participant's net contribution reflects both adjustments and the overall score changes accordingly. Given multiple overlapping soft-block penalties of the same driver in a slot, When diminishingReturns is enabled, Then marginal penalties decrease per overlap and the total penalty does not exceed the configured cap for that driver. Given any configuration is applied, Then metadata includes configVersion and the effective driverWeights.
Conservative Heuristics for Incomplete or Conflicting Data
Given missing holiday data for a participant locale, When scoring, Then the engine assumes observance for official holidays on the slot date, applies a conservative penalty, and records an assumption in metadata.assumptions[]. Given absent individual preferences, When scoring, Then only company policies and cultural context are applied; no null dereferences occur; if the slot is outside standard work hours, a conservative neutrality or mild penalty is applied. Given conflicting inputs (e.g., work hours overlapping a siesta window), When scoring, Then the stricter constraint prevails resulting in the higher penalty, and the conflict is recorded in metadata.assumptions[]. Given any heuristic was applied, Then metadata.assumptions[] is non-empty and references affected drivers and participants.
Score Bounds and Sensitivity to Early/Late Hours
Given a slot fully within overlapping work hours for all participants with no cultural conflicts, Then overallScore = 100. Given a slot 2 hours before the earliest participant's work window, When the slot is moved toward the window in 30-minute increments, Then the penalty decreases (score increases) monotonically until inside the window. Given a slot 3 hours after the latest participant's work window with late-hour sensitivity ≥ early-hour sensitivity, Then the late-hour penalty is at least as severe as the symmetric early-hour case. Given any computed result, Then overallScore is clamped within [0, 100].
Score Explainability & Driver Breakdown
"As a team lead, I want to see what factors lowered a slot’s respect score so that I can explain the decision and adjust the proposal if needed."
Description

UI and API surfaces that reveal why a slot or series scored as it did. Presents a breakdown of top drivers (e.g., “Conflicts with siesta in Madrid: −18”, “Within preferred focus hours in NYC: −12”), per-participant impact, and visual overlays on the timeline. Includes tooltips, an expandable details panel, and accessibility support (keyboard, screen readers). Offers “why not higher?” diagnostics and highlights the minimal change needed to reach target thresholds (e.g., +30 minutes shift). Data is exportable for sharing with stakeholders.

Acceptance Criteria
Inline Driver Breakdown on Hover
Given a proposed slot is visible on the timeline and has a Respect Index score When the user hovers with a pointer or focuses via keyboard on the slot’s score pill Then a tooltip appears within 150ms displaying the top 5 drivers by absolute point impact, each with label and signed point value (e.g., −18, +12) And the tooltip indicates the total score and percentage contribution of each listed driver And the tooltip updates to reflect the currently highlighted slot and hides within 150ms when focus/hover leaves And values in the tooltip sum (within ±1 point rounding tolerance) to the displayed total score And the tooltip meets WCAG 2.2 AA for color contrast and is screen-reader discoverable via aria-describedby
Expandable Details Panel with Per-Participant Impact
Given a slot with multiple participants When the user activates the Details expand control Then an anchored panel opens within 300ms showing per-participant local time, impact drivers, and net point impact for each participant And each driver row shows driver label, category (e.g., holiday, siesta, focus), and signed point impact And the sum of all participant net impacts equals the slot total score (±1 rounding) And the panel allows sorting by impact magnitude and filtering by participant And closing/reopening persists the last selected sort and filter within the current session
Timeline Visual Overlays Toggle
Given the timeline view contains participants across time zones When the user toggles Show respect overlays Then shaded bands or icons render for active drivers (e.g., local holidays, siesta windows, focus blocks) aligned to each participant’s local time bands And a legend explains overlay colors/symbols and can be toggled per driver type And overlays appear or hide within 250ms and do not change the underlying score And hovering or focusing an overlay reveals a tooltip with driver label, local start–end times, and point impact range And toggling off hides overlays with no residual artifacts
Why-Not-Higher Diagnostics and Minimal Shift Suggestion
Given a target Respect Index threshold is set (default 80) When the slot score is below the threshold Then a Why not higher? control is visible And activating it opens diagnostics listing the top 3 blocking drivers with their point impacts and a minimal change suggestion (e.g., +30 minutes later) to reach the target And at least one one-click adjustment control is offered; applying it updates the slot time and recalculates the score within 500ms, reflecting the new score And if the threshold is unreachable, the UI states Unreachable and presents the closest achievable score and suggestion And for a series, diagnostics show the percentage of occurrences that would reach the target with the suggested shift and allow apply-to-series
Explainability API Response
Given an authenticated client requests GET /v1/respect/explainability?slot_id={id} When the slot exists and the client has access Then the API responds 200 within 400ms P95 under 50 concurrent requests with JSON containing: slot_id, total_score, drivers[], participants[], target_diagnostics, version And each drivers[] item includes id, label, category, signed_points, participant_ids[], rationale, and time_window (ISO 8601) And the sum of drivers[].signed_points equals total_score (±1 rounding) And participants[] includes id, local_time (ISO 8601), net_impact, and top_drivers[] And invalid or unauthorized requests return appropriate 400/401/404 with error codes and messages per OpenAPI spec And the OpenAPI schema is published and matches the actual response
Stakeholder Export (CSV and JSON)
Given a user has selected one or more slots or a series When the user clicks Export and chooses CSV or JSON Then a file downloads named timeglue-respect-explainability-{YYYYMMDD-HHmmssZ}.{csv|json} And the export includes for each slot: slot_id, total_score, top drivers with signed points, per-participant net impacts, driver rationales, and suggested minimal change (if any) And timestamps are ISO 8601 with timezone offsets; numeric fields use dot decimal; CSV uses UTF-8 with headers and comma delimiter And exports respect access controls and exclude participants marked as private (replacing with anonymized labels) And exporting the currently filtered view only includes items visible under the current filters
Accessibility: Keyboard and Screen Reader
Given a user navigates without a mouse and/or with a screen reader When interacting with the score pill, tooltip, overlays toggle, and details panel Then all interactive elements are reachable via Tab/Shift+Tab with visible focus indicators And tooltips and the details panel are operable via keyboard (Enter/Space to open, Escape to close) and do not trap focus And semantic roles and ARIA attributes expose labels and descriptions so that VoiceOver and NVDA read driver labels with signed points and the total score And contrast ratios meet WCAG 2.2 AA; motion respects reduced motion settings; components pass axe-core scanning with zero critical violations
One‑Click Respect Improvements
"As a meeting organizer, I want one‑click suggestions to improve a slot’s respect score so that I can fix issues quickly without manual trial and error."
Description

Actionable suggestions that automatically adjust proposals to improve respect without violating required constraints. Generates nearby alternatives, series offsets (e.g., start 15 minutes later), duration tweaks, and rotating inconvenience patterns. Shows projected new score and participant impact before applying. Integrates with FairShare to jointly optimize equity and respect, offering trade‑off sliders (e.g., maximize respect with minimum fairness deviation). Supports undo, applies changes to invites/links, and logs decisions for audit.

Acceptance Criteria
One-Click Nearby Alternatives for Single Meeting Proposal
Given a proposed meeting slot with participants and required constraints (work hours, holidays, focus blocks) defined and a current Respect Index score When the user clicks "Improve Respect" and chooses "Nearby Alternatives" Then the system returns one or more alternative slots within the configured proximity window And each alternative has a projected Respect score greater than the current slot And no alternative violates required constraints for any participant And each alternative displays the projected Respect score, score delta, and top score drivers (e.g., observances, siestas, family windows) And alternatives are ranked by projected Respect score descending with deterministic tie‑breakers (least after‑hours impact, then earliest chronological) And if no valid alternative exists, the system displays "No improvement options" with reasons and no changes are applied
Single-Series Offset Suggestion (15-Minute Shift)
Given a recurring series with fixed duration and a current Respect Index score When the user clicks "Improve Respect" and selects "Series Offset" Then the system proposes offset options that shift start times by configured increments (e.g., ±15 minutes) without changing dates And each option honors required constraints for every occurrence in the selected scope And the preview shows the net Respect score change across the series and per‑occurrence highlights And the user can choose "Apply to all future occurrences" or "Apply from selected date forward" And if no feasible offset exists within constraints, the UI shows "No feasible offset" with the blocking constraints identified
Duration Tweak Suggestion with Agenda Minimum
Given a meeting proposal with a defined agenda minimum duration When the user clicks "Improve Respect" and selects "Duration Tweak" Then the system suggests shorter durations in configured increments that are greater than or equal to the agenda minimum And each suggestion maintains required participant availability and required constraints And the preview shows the projected Respect score delta and the reduction in after‑hours overlap And applying the suggestion updates only the duration (not the start time or date) And if no duration reduction meets the agenda minimum or constraints, the UI indicates no valid tweaks with reasons
Rotating Inconvenience Pattern Across Series
Given a multi‑timezone recurring series with at least two participant regions When the user clicks "Improve Respect" and selects "Rotate Inconvenience" Then the system generates a rotation plan that distributes early/late occurrences across participant regions per the configured rotation rules And the plan ensures no participant exceeds the configured maximum number of after‑hours occurrences within the series And all rotated occurrences honor required constraints and maintain the series cadence And the preview lists per‑participant counts of early/late/after‑hours instances and the projected series‑level Respect score change And applying the plan updates calendar invites and scheduling links for all affected occurrences
Pre-Apply Projection of Respect Score and Participant Impact
Given any improvement suggestion is selected When the user opens the preview panel Then the UI displays the projected new Respect score and the delta versus current And the UI displays per‑participant local times, after‑hours flags, and conflict indicators (observances, siestas, family windows) And any suggestion that would violate required constraints is labeled Infeasible and the Apply action is disabled And the preview shows the exact changes that will be applied to invites and scheduling links if confirmed
Joint Optimization with FairShare Trade-Off Sliders
Given FairShare is enabled for the workspace and a meeting or series is selected When the user adjusts the trade‑off slider to "Maximize Respect with fairness deviation ≤ X%" Then the optimizer returns one or more candidate schedules whose fairness deviation is ≤ X% and whose Respect score is maximized within that bound And the UI displays Respect and FairShare scores for each candidate with score deltas versus current And if no candidate satisfies the bound, the UI shows "No feasible options" and displays the tightest achievable fairness deviation so the user can relax the slider And applying a candidate updates the meeting or series as previewed
Undo Applied Improvement with Invite/Link Reversion and Audit Log
Given an improvement was applied to a meeting or series in the last action When the user clicks Undo Then the system restores the prior state of the meeting or series, including times, duration, rotation plan, and settings And calendar invites and scheduling links are reverted to their previous values and participants receive appropriate update notifications And the audit log records both the apply and undo actions with user, timestamp, before/after state summary, Respect/FairShare score deltas, and rationale (selected drivers) And only the most recent improvement is undone and Undo is disabled until another improvement is applied
Policy Weighting & Team Overrides
"As an admin, I want to customize how respect is calculated for my organization so that scores reflect our culture and operating model."
Description

Configurable framework for admins and team owners to tailor the Respect Index to organizational norms. Provides preset templates (e.g., EMEA‑first, APAC‑friendly) and granular weights for penalties (late evening, Friday afternoons, prayer times) and boosts (core overlap windows). Supports per-team, project, and user-level overrides; temporal exceptions; and versioned policies with effective dates. Includes a simulation mode to preview how policy changes affect typical schedules before rollout.

Acceptance Criteria
Org Policy Template Application & Persistence
Given I am an Organization Admin with access to policy settings When I apply the "APAC-friendly" preset template and click Save Then a new Active policy version is created with weight keys and values exactly matching the template definition and an effective timestamp of now Given the created policy version ID When I retrieve the policy configuration Then the returned configuration equals the saved weights and includes the correct version identifier Given the new policy version is active When Respect Index is computed for any schedule context with no overrides Then the engine uses the org-level template weights for scoring
Granular Weight Adjustment Impacts Scores
Given an existing policy version and a fixed set of proposed meeting slots When I increase the "late evening" penalty weight by a positive amount and save a new version Then, when recomputing Respect Index on the same slots, every slot overlapping the defined late evening window has a strictly lower score than before and all other slots’ scores remain unchanged Given I decrease the "core overlap window" boost by a positive amount and save a new version When I recompute scores on the same slots Then only slots within core overlap windows have a lower score relative to before and slots outside those windows are unchanged Given weights are edited for multiple categories When I recompute scores Then the total score for each slot equals the sum of category contributions computed with the new weights
Overrides Precedence and Isolation
Given an org policy, a team override, a project override, and a user override are all defined for the same weight category When computing Respect Index in a context involving that user on that project within that team Then the effective weight used follows specificity precedence: user override > project override > team override > org policy Given two teams A and B where only team A has an override When computing scores for a schedule involving only team B members Then the team A override does not affect the scores Given a project override exists on project P When computing scores for a different project Q Then project P’s override is not applied
Temporal Exceptions and Effective-Dated Policies
Given a new policy version with effective timestamp T (UTC) When computing scores for a meeting with start time < T Then the previous effective policy version is used Given the same policy version and a meeting with start time >= T When computing scores Then the new policy version is used Given a temporal exception window configured to mute the "Friday afternoon" penalty between dates D1 and D2 When computing scores for meetings within [D1, D2] that fall on Friday after the configured afternoon threshold Then the "Friday afternoon" penalty contribution is zero for those meetings
Simulation Mode Preview Without Persisting
Given unsaved changes to policy weights exist in Draft When I run Simulation on a selected schedule cohort Then the system returns, for each slot: baseline score, simulated score, per-category contribution breakdown for both, and the score delta, and marks the changes as not persisted Given I exit Simulation without publishing When I recompute scores in production Then the baseline (pre-simulation) policy version remains in effect Given I choose "Publish simulated changes" with a future effective timestamp When I confirm the publish Then a new policy version is created with those weights and the specified effective timestamp, and simulation results match production results for meetings occurring on/after that timestamp
Version History and Rollback
Given multiple policy versions exist When I view version history Then I can see version IDs, authors, created timestamps, effective timestamps, and summaries of changes for each version Given I select a prior version V and choose Rollback Now When I confirm the rollback Then a new version V' is created with weights equal to V and an effective timestamp of now, and history shows a rollback event linking V' to V Given a policy version is rolled back When scores are computed after the rollback time Then the engine uses the reinstated weights
Authorization and Scope Control
Given my role is Organization Admin When I access policy settings Then I can create, edit, and publish org-level policies and apply preset templates Given my role is Team Owner for team X and not an Organization Admin When I access policy settings Then I can create and edit overrides for team X only and cannot modify org-level policy Given my role is Project Owner for project Y When I access policy settings Then I can create and edit project-level overrides for project Y only Given I am end user Z When I access my policy exceptions Then I can create and edit my own user-level overrides only Given a user attempts an action outside their scope When the request is processed Then the action is rejected with a permission error and the attempt is logged
Audit Trail & Stakeholder Reporting
"As a program manager, I want a clear record of why a time was chosen so that I can defend scheduling decisions to stakeholders and leadership."
Description

End‑to‑end logging of scores, selected options, overrides, and improvement actions to create a defensible record. Generates shareable reports that summarize the chosen slot/series, alternatives considered, rationale, and impact by participant/region. Supports export to PDF/CSV and redaction of sensitive details. Includes retention settings and access controls to meet compliance needs.

Acceptance Criteria
Immutable Audit Log of Respect Index Decisions
Given a scheduling session, When Respect Index scores are computed for proposed slots or series, Then an audit event is appended for each computation capturing: UTC timestamp, session ID, slot/series ID, score value, scoring inputs snapshot ID, algorithm version, and requesting user ID. Given a user confirms a selected slot or series, When the decision is saved, Then an audit event is appended capturing: selected slot/series ID, candidate set IDs visible at decision time and their scores, and any user-provided rationale. Given a one-click improvement or manual override is applied, When the change is saved, Then an audit event is appended capturing: action type, before/after values, affected participants/regions, and system-generated justification text. Given the audit log, When any update or delete is attempted on existing events, Then the system prevents mutation (append-only) and returns an error while logging a security event.
Stakeholder Report: Chosen Option, Alternatives, and Rationale
Given an audited scheduling session, When a stakeholder report is generated, Then it includes: chosen slot/series with per-participant local times, top 10 alternatives considered at decision time with their Respect Index scores, and explicit rationale including any overrides and improvements applied. Given the generated report, When reviewing impact sections, Then it shows for each participant and region: work-hours compliance (yes/no), after-hours minutes delta, observance/conflict flags, and score contribution breakdown. Given the web report, When compared to exported versions, Then content parity holds (same data, same redaction state, same metrics).
Export Fidelity and Performance (PDF/CSV)
Given a generated stakeholder report, When exporting to PDF, Then the PDF matches the web report content and redaction state, is under 10 MB for up to 200 participants and 500 proposed slots, and completes within 10 seconds at the 95th percentile. Given export to CSV is requested, When files are generated, Then alternatives.csv contains one row per alternative with columns: session_id, slot_or_series_id, utc_start, utc_end, in_hours_pct, respect_index_score, rationale_summary; and impact_by_participant.csv contains: session_id, slot_or_series_id, participant_id, region, local_start, local_end, in_hours_flag, after_hours_minutes, observance_conflicts, score_contribution. Given exported files, When inspecting metadata and filenames, Then both include report ID and generation timestamp.
Selective Redaction for Sensitive Details
Given a report prepared for sharing, When the user selects redaction options (names, emails, calendar titles, focus block labels, justification notes), Then the preview replaces those fields with 'REDACTED' consistently across all views. Given redaction is enabled, When exporting or sharing via link, Then redacted content is irrecoverable: omitted from CSV, burned into PDF (not overlay), excluded from embedded JSON, and absent from file/document metadata. Given a redacted report, When viewing the Redaction Summary, Then it lists which fields were redacted, by whom, and when.
Access Controls and Shareable Links
Given workspace roles Owner, Editor, and Viewer, When accessing reports, Then Owners and Editors can view full (non-redacted) reports, configure redaction, and generate exports; Viewers can only view redacted reports and cannot export raw logs. Given a shareable link is created, When an expiration datetime and access scope are set, Then the link enforces view-only scope, expires at the configured time, supports one-click revocation, and all accesses are logged with timestamp, IP, and user ID when available. Given access beyond scope or after expiration, When a user attempts to open the link, Then the system responds with a forbidden error and logs a security event.
Retention Policy and Legal Hold
Given a workspace retention period in days is configured, When audit events or reports exceed the retention period, Then they are scheduled and permanently purged within 24 hours, with a purge audit event recorded. Given a legal hold is placed on a session or report, When retention would otherwise purge it, Then the records are preserved until the hold is released; upon release, retention resumes based on original creation date. Given purged objects, When requested via UI or API, Then regeneration is blocked and the system returns a 'gone' response indicating permanent deletion.
Traceability of Score Inputs and Algorithm Versions
Given Respect Index computations occur, When logged, Then each event references a versioned scoring model ID and includes a link to the explainability payload listing drivers (observances, siestas, family windows, local customs) and their weightings for that score. Given a scoring model version changes between computation and selection, When generating the report, Then the version change is highlighted and optional recomputed score deltas are shown if requested by the user. Given overrides to weights or inputs, When applied, Then the system logs who, when, and why, and surfaces this in the report rationale and audit log.
Smart Link & Calendar Embed with Respect Badge
"As an external invitee, I want to see a simple respect score and why it was chosen so that I can trust the organizer considered my local context."
Description

Extends Timeglue smart links and calendar invites to display a Respect Index badge and a concise explanation of key drivers. Ensures recipients can view score context without logging in, with privacy safeguards and minimal data exposure. Embeds into ICS descriptions and integrates with Google/Microsoft add‑ons where available. Provides themeable, accessible components and localized content.

Acceptance Criteria
Smart Link displays Respect Badge and drivers to unauthenticated recipients
Given a Timeglue Smart Link with proposed meeting slots and a computed Respect Index When an unauthenticated recipient opens the link Then a Respect Index badge renders within the page header within 1.5 seconds on a 3G Fast profile And the badge shows a numeric score between 0 and 100 and a category label derived from thresholds: Low (0–49), Medium (50–74), High (75–100) And a "Why" control reveals the top 3 score drivers with short labels and icons And no participant names, emails, or calendar IDs are displayed And the displayed score and drivers match the backend values for the currently selected slot
ICS export embeds Respect Index summary safely
Given an event created from Timeglue with Respect Index data When a recipient downloads the ICS and opens it in a standards-compliant calendar Then the DESCRIPTION field contains a block starting with "Respect Index:" including the numeric score, category, and up to 3 drivers And the Respect Index block is limited to 500 characters and the total DESCRIPTION is <= 3500 characters And DESCRIPTION lines are folded per RFC 5545 (max 75 octets per line) And the block contains no participant names, emails, or raw calendar IDs And the block includes a public, tokenized URL to timeglue.com/r/{token} with at least 128 bits of entropy
Google Calendar add-on displays inline Respect Badge
Given the Timeglue Google Workspace Add-on is installed for the user And an event was created via a Timeglue Smart Link or ICS with a Respect Index When the user opens the event in Google Calendar on the web Then the add-on side panel displays a Respect Index badge with numeric score and category matching the event’s slot And selecting "Why" reveals up to 3 drivers And if the add-on is not installed, the ICS DESCRIPTION still shows the Respect Index summary
Microsoft Outlook add-in displays inline Respect Badge
Given the Timeglue Outlook add-in is installed for the user (web or desktop) And an event was created via a Timeglue Smart Link or ICS with a Respect Index When the user opens the event Then the add-in task pane displays a Respect Index badge with numeric score and category matching the event’s slot And selecting "Why" reveals up to 3 drivers And if the add-in is not installed, the ICS body still shows the Respect Index summary
Public respect view uses expiring, revocable tokens and minimal data
Given a public tokenized Respect View URL is generated for an event When an unauthenticated recipient opens the URL Then only aggregated cultural drivers, slot time in the viewer’s local time zone, and the score are shown And no participant names, emails, calendar IDs, or precise home locations are shown And the token is unguessable (>= 128 bits of entropy), expires no later than 30 days after the event end, and can be revoked by the organizer at any time And after expiration or revocation, the URL returns HTTP 410 Gone
Badge component is themeable and accessible
Given the badge is rendered on web surfaces When theme is set via data-theme (light/dark) or brand color variables Then badge colors update without reducing contrast below WCAG AA (>= 4.5:1 for text, >= 3:1 for large text/icons) And the badge and "Why" control are keyboard navigable (Tab/Shift+Tab) and operable via Enter/Space And screen readers announce "Respect Index, {score}, {category}" and "Why: reveals drivers" via ARIA labels And motion is reduced when prefers-reduced-motion is enabled
Localized badge content and time formats
Given the recipient’s Accept-Language header or an explicit lang parameter (e.g., ?lang=es-ES) When the badge and drivers render Then all static text and driver labels are localized for at least en, es-ES, fr-FR, de-DE, and pt-BR with fallback to en And numeric scores and dates/times format according to locale (e.g., 12h vs 24h) And untranslated keys never display; fallback strings are used instead

Polite Context

Adds a friendly note to invites and smart links explaining how the chosen time respects local customs (without exposing personal details). Builds trust with customers and candidates, reduces reschedules, and signals your team’s cultural care. Templates localize automatically by language and region.

Requirements

Context Reasoning Engine
"As a remote team lead, I want the system to automatically explain why a selected time respects each participant’s local customs so that I build trust and reduce rescheduling."
Description

Deterministically generates a concise, human-friendly explanation for why a proposed meeting time is considerate for each recipient, based on their locale, region-specific holidays and working week patterns, and configured working hours/focus blocks—without revealing personal details. Consumes Timeglue’s availability model, holiday calendars, and recipient locale signals to produce normalized “reason codes” (e.g., respects_work_hours, avoids_holiday, avoids_lunch_hour) mapped to message templates. Supports multi-recipient meetings by selecting the most universally considerate rationale or per-recipient variants where supported. Handles edge cases (e.g., daylight saving transitions, Friday/Sunday weekends, half-days) and exposes a stable API for downstream rendering in links, invites, and emails with sub-200ms latency and idempotent outputs.

Acceptance Criteria
Deterministic Single-Recipient Explanation Within Work Hours
Given a recipient with locale "en-US", working hours 09:00–17:00, and a focus block 13:00–14:00 And a proposed meeting at 10:30 local time that does not overlap the focus block When the engine generates context reasons Then reason_codes includes ["respects_work_hours"] and excludes ["violates_focus_block", "after_hours"] And rendered_message is generated using the appropriate template for "en-US" And rendered_message does not include exact working-hour times, calendar names, or event titles And rendered_message length is <= 150 characters And repeated calls with identical inputs return byte-identical reason_codes and rendered_message
Holiday and Regional Weekend Respect
Given a recipient with locale "ar-AE" whose working week is Sunday–Thursday and a public holiday on 2025-12-02 And a proposed meeting on 2025-12-03 11:00 local time (first working day after the holiday) When the engine generates context reasons Then reason_codes includes ["avoids_holiday", "respects_work_week"] and excludes ["on_public_holiday"] And rendered_message is localized in Arabic (ar) with region-aware phrasing for AE And rendered_message does not reveal personal calendar details or exact shift lengths And outputs are deterministic across repeated identical requests
Half-Day Schedules and Lunch Hour Avoidance
Given a recipient with locale "en-IN" who has a recurring Friday half-day (09:00–12:00) and a daily lunch hour 12:30–13:30 And a proposed Friday meeting at 10:30 local time When the engine generates context reasons Then reason_codes includes ["respects_half_day", "avoids_lunch_hour"] and excludes ["after_hours", "during_lunch_hour"] And rendered_message succinctly indicates consideration without listing exact times And outputs remain idempotent across identical inputs
Multi-Recipient Universal vs Per-Recipient Rationale Selection
Given three recipients: A (en-GB, 09:00–17:30), B (en-US, 09:00–17:00 with lunch 12:00–13:00), C (he-IL, working week Sun–Thu) And a proposed meeting Monday 15:00 UTC that falls within all recipients’ working hours and outside B’s lunch hour When the engine generates context reasons for a channel that does NOT support per-recipient variants Then it returns a single rationale whose reason_codes apply to 100% of recipients (e.g., ["respects_work_hours"]) and excludes recipient-specific-only reasons And the rendered_message uses a neutral, channel-appropriate template in the organizer’s language When the engine generates context reasons for a channel that DOES support per-recipient variants Then it returns a per_recipient array with localized rendered_message and matching reason_codes per recipient And no pair of outputs contain contradictory reason codes (e.g., one says "on_holiday" while another says "avoids_holiday")
Daylight Saving Transition Handling
Given a recipient in time zone Europe/Berlin with DST starting 2025-03-30 at 02:00 (clocks jump to 03:00) And a proposed meeting on 2025-03-31 10:00 Europe/Berlin time When the engine generates context reasons Then local-time computations use the correct post-transition UTC offset And reason_codes includes ["avoids_dst_transition"] if the proposed time is not on the transition window And rendered_message shows an unambiguous local date and time and excludes UTC offset leakage when not needed And outputs are deterministic across identical inputs
API Contract, Latency, and Idempotency
Given a POST to /v1/context-reasons with a valid auth token, recipients[], proposed_time (ISO 8601 with time zone), availability_snapshot_id, and locale signals When the request is processed under nominal load (<= 50 RPS) with warm calendar caches Then the response conforms to the published JSON schema, including fields: request_id, api_version, channel, universal_rationale | per_recipient, recipients[].reason_codes, recipients[].rendered_message, recipients[].locale And P95 end-to-end latency is <= 200 ms and P99 is <= 300 ms And repeated identical requests return byte-identical responses (including reason_codes order and rendered_message content) And non-determinism sources (e.g., unordered maps) are normalized before output
Localization and Privacy Safeguards
Given inputs containing internal calendar names, event titles, email addresses, or precise working-hour bounds When the engine renders messages in the detected recipient language (with region fallback) or default English if unsupported Then rendered_message contains no personal data (no names, email addresses, calendar labels, street addresses, or event titles) And rendered_message does not expose exact working-hour bounds; only neutral phrasing is allowed (e.g., "within their typical working hours") And a PII detector run on rendered_message yields zero matches for emails, phone numbers, or addresses And reason_codes map only to approved templates for the chosen locale; if a template is missing, a safe default template is used
Locale-aware Templates & Translation
"As a recruiter inviting candidates worldwide, I want the note to appear in their language and culturally appropriate style so that it feels respectful and easy to understand."
Description

Provides a template library that localizes the polite note by language and region, with support for pluralization, regional variants (e.g., en-GB vs en-US), and culturally appropriate phrasing. Templating binds to reason codes and safe placeholders (e.g., weekday name, local time window) and applies tone rules while enforcing brevity limits. Includes translation management (import/export, versioning, fallback chains), QA tools (pseudo-localization, automated checks for placeholder coverage), and runtime locale resolution from recipient signals (link parameters, browser language, calendar attendee locale) with graceful fallbacks.

Acceptance Criteria
Runtime Locale Resolution and Fallback Chain
Given a recipient with link parameter locale=pt-BR, browser language=fr-FR, calendar attendee locale=en-US, and account default=en-GB And templates exist for en-GB and en When the polite note is rendered Then the runtime selects locale pt-BR And if pt-BR is unavailable it falls back to pt And if pt is unavailable it falls back to en-GB And if en-GB is unavailable it falls back to en And the resolved locale and resolution path (source and fallbacks) are recorded in telemetry
Regional Variant Selection and Reason Code Binding (en-GB vs en-US)
Given templates for reason_code=HonorsWorkHours exist in en-GB and en-US And {local_time_window} is available from scheduling context When rendering for locale en-GB with local_time_window=09:00–17:00 Then the output uses the en-GB template variant And the time window is formatted as 24-hour "09:00–17:00" And the copy uses British phrasing/spelling (e.g., "honour(s)") And no personal data is included When rendering for locale en-US with the same time window Then the output uses the en-US template variant And the time window is formatted as 12-hour "9:00 AM–5:00 PM" And the copy uses American phrasing/spelling (e.g., "honor(s)") And if a reason_code-specific template is missing in the target variant, the system falls back to the locale’s generic template, then to the base language generic template
Pluralization Rules for Time Units
Given a template includes an ICU plural for {days_until_holiday} When rendering in locale en with days_until_holiday=1 Then the text contains "1 day" When rendering in locale en with days_until_holiday=2 Then the text contains "2 days" When rendering in locale ru-RU with days_until_holiday values 1, 2, and 5 Then the plural categories one/few/other are applied respectively, producing "1 день", "2 дня", and "5 дней" And ICU/CLDR plural rules are used for all supported locales
Safe Placeholders and Placeholder Coverage Validation
Given the allowed placeholders list includes {weekday_name}, {local_time_window}, and {region_name} When validating a template that uses only allowed placeholders Then validation passes and the template is publishable When validating a template that includes an unknown placeholder such as {email} or {full_name} Then validation fails with error code PLH_UNKNOWN and the template cannot be published When running automated placeholder coverage on translations Then any locale missing a required placeholder is flagged with PLH_MISSING and blocked from release until fixed And runtime rendering proceeds only for locales with 100% placeholder coverage for that template
Brevity Limit and Tone Rules Enforcement
Given the maximum rendered note length is 200 Unicode characters When a rendered note would exceed 200 characters for any locale Then the system selects the registered "short" variant for that locale and reason_code if available And if no short variant exists, it truncates at the last word boundary before 200 characters and appends "…" And the rendered note contains no more than 1 exclamation mark And the rendered note contains no words of 5+ letters in ALL CAPS And the rendered note contains at least one courtesy term from the locale’s tone lexicon (e.g., en: "please"; es: "por favor"; fr: "merci") And the note includes no personal identifiers (emails, phone numbers, precise addresses)
Translation Management: Import/Export and Versioning
Given an admin exports the current translation set When exporting Then files are produced in JSON and XLIFF formats containing keys, locale codes, version numbers, and ICU placeholders intact When a translator edits XLIFF and re-imports Then existing entries update in place, version numbers increment, and author/timestamp metadata are recorded And placeholder mismatches or removals are rejected with TR_PLACEHOLDER_MISMATCH And a dry-run import produces a diff summary with counts of added/updated/deleted strings And after a successful import, the new versions are available to runtime within 5 minutes
QA Tools: Pseudo-localization and Automated Checks
Given pseudo-localization mode is enabled When rendering any template Then alphabetic characters are replaced with accented variants and wrapped in [⟪ ⟫], increasing length by 30% ±10% And placeholders (e.g., {weekday_name}) remain unchanged When running automated QA checks Then failures are raised for: missing placeholders, untranslated strings identical to source, strings under 3 characters post-pseudo-localization, or disallowed punctuation for the locale profile And builds are blocked on critical QA failures while warnings are logged without blocking
Smart Link and Invite Injection
"As a sales rep sharing a booking link, I want the polite context to display on the booking page and be included in the calendar invite so that recipients understand the consideration no matter how they book."
Description

Integrates polite context into Timeglue smart booking links and calendar invites. Renders the note dynamically on the booking page as slots are selected and includes it in confirmation emails and event descriptions for Google Calendar, Outlook, and ICS exports using formatting-safe markup. Ensures compatibility with existing invitation content, prevents duplication on updates, and respects per-recipient variants when possible. Provides feature flags and API hooks for partners, and maintains accessibility (ARIA labels, screen-reader friendly) across web and mobile.

Acceptance Criteria
Dynamic Polite Context on Booking Page
Given a Timeglue smart link booking page with locale auto-detected or user-selected When the attendee selects, changes, or clears a time slot Then the polite context note appears, updates, or hides accordingly within 300ms without page reload And the note references applicable local customs for the selected slot and attendee time zone (e.g., work hours, public holidays, focus blocks) based on configured calendars And the note contains no personally identifiable information (no name, email, exact location, IP, or device details) And the note is localized to the attendee's language-region; if unavailable, falls back to en-US And the content is consistent between desktop and mobile views
Injection into Confirmation Emails and Calendar Events
Given a booking is confirmed via a Timeglue smart link When confirmation emails and calendar events are generated Then the polite context is included in: email body (plain text and basic HTML), Google Calendar event description, Outlook event body, and ICS DESCRIPTION And for HTML channels, markup is sanitized and limited to bold, italic, and hyperlink tags; for ICS, text is plain and RFC 5545-compliant (line folding and escaping) And the ICS validates with no errors and is accepted by Google and Outlook clients And the note appears once and is placed after any host-provided description, preceded by a clear separator
Idempotent Updates and No Duplication
Given an event previously injected with a polite context block tagged with a hidden marker When the event is rescheduled, attendees are modified, or the host updates the description Then the system updates or replaces the existing polite context block in place And duplicate polite context blocks are not created after repeated syncs or retries (idempotent behavior) And if the computed polite context is unchanged, the event body remains byte-identical and does not trigger additional notifications (where provider supports) And all updates are logged with a correlation ID for traceability
Per-Recipient Localization and Variants
Given multiple recipients with different locales and time zones When sending recipient-specific emails or in-app confirmations Then each recipient receives a polite context variant localized to their language-region and framed from their time-zone perspective And the shared calendar event description remains accurate for all recipients using neutral phrasing that avoids per-recipient specifics And if per-recipient personalization is not supported by a channel, the system falls back gracefully and records the fallback in logs And templating applies gender-neutral and culturally appropriate wording per locale style guide
Accessibility Compliance for Polite Context
Given the booking page is accessed on web desktop and mobile When the polite context note appears or updates Then it is exposed via an aria-live="polite" region with a descriptive aria-label And text meets WCAG 2.2 AA contrast and remains readable at 200% zoom without loss of content or functionality And the component is fully keyboard-navigable with preserved focus order; updates do not steal focus And screen readers (NVDA, JAWS, VoiceOver, TalkBack) announce the update in manual testing And interactive targets meet a minimum 44x44 dp on touch devices
Feature Flags and Partner API Hooks
Given account-level and link-level feature flags for Polite Context When the flag is disabled for an account or specific link Then no polite context is rendered on booking pages or injected into emails/events for that scope When the flag is enabled Then rendering/injection becomes active within 60 seconds without redeploy or cache purge And partner API endpoints allow preview, inject, suppress, and template override actions with OAuth2 auth, 60 rpm rate limits, and structured error codes And webhooks emit context_injected and context_updated events signed with HMAC for verification
Compatibility and Formatting Safety
Given an invite with an existing host description, rich text, and integrations (e.g., conferencing links) When injecting the polite context Then all existing content is preserved verbatim and ordering is maintained And the polite context is wrapped with safe markers and total body size remains under provider limits (Google 8192 chars, Outlook 10240 chars, ICS 64KB) And if adding the note would exceed limits, the system truncates the note with an ellipsis or omits it and logs a warning; booking still succeeds And HTML is sanitized to strip scripts/styles/unsupported tags; a plain-text fallback is provided for clients that strip HTML And automated snapshots confirm no visual regressions across supported clients
Privacy & Redaction Guardrails
"As a security-conscious admin, I want guarantees that the notes cannot leak sensitive information so that we remain compliant and preserve trust."
Description

Enforces strict privacy rules so polite notes never expose personal details or sensitive attributes. Limits content to high-level considerations (e.g., within local work hours, avoids holiday) and automatically redacts or blocks phrases that could reveal individual schedules (exact focus block names), health, religion, or other sensitive data. Includes a compliance rules engine, PII detection, allow/deny lists, audit logs, and region-aware policy presets. Defaults to safest messaging when signals are ambiguous, and documents processing purposes for DPA/record-keeping.

Acceptance Criteria
PII Detection and Redaction in Polite Notes
Given a polite note is generated from templates and dynamic inputs And the candidate content contains PII such as email addresses, phone numbers, street addresses, social handles, or full names When the note is generated or previewed Then all PII tokens are detected with ≥99% precision and ≥97% recall on the benchmark dataset And each detected token is redacted with a standardized placeholder "[redacted]" or removed per policy And the final note contains zero PII as verified by the PII validator And the redaction action is recorded with hashed originals and rule identifiers
Sensitive Attributes Blocking with Allow/Deny Lists
Given the candidate note includes terms or inferences related to sensitive attributes (health, religion, sexual orientation, union membership, political opinions) Or matches any term on the deny list When generating the polite note Then those terms are removed or the note is replaced with the generic safe note "This time respects local working norms." And no explicit or implicit sensitive attribute references remain in the output And allow-listed terms are permitted only if they are not categorized as sensitive attributes by the rules engine And the rules triggered and decisions are recorded in the audit log
Redaction of Focus Block Names and Schedule Hints
Given the system has access to calendar metadata that includes focus block titles or personal event names And a polite note would otherwise reference them When generating the polite note Then event titles, focus block names, durations, and recurrence patterns are not included And any such references are replaced with generic terms like "focus time" or "personal commitment" And the output contains no tokens matching the original event titles And time references remain qualitative only (e.g., "within local work hours") without exact times or durations
High-Level Messaging Only (No Specifics)
Given any locale and invite context When composing the polite note Then the note uses only high-level considerations from the allow list (within work hours, avoids public holiday, avoids local rest day, respects lunch window) And the note does not contain exact time notations (no patterns like HH:MM or AM/PM), named personal routines, or individual-specific details And if multiple considerations apply, only the highest-priority single rationale is included And the English note length is ≤160 characters (scaled by locale factor 0.8–1.2)
Region-Aware Policy Presets Enforcement
Given the organizer or invitee regions are known When generating the polite note Then the correct region-aware policy preset is automatically selected (e.g., GDPR-Strict for EU/EEA) And the preset’s rules are enforced, including blocking sensitive categories per regional regulations And the selected preset ID is recorded in the event metadata And preset selection accuracy is 100% across the supported region test matrix
Ambiguity Handling with Safest Default Messaging
Given any required signal (holiday, locale, time zone, cultural convention) has confidence < 0.7 or is missing When generating the polite note Then the system enables safe_mode and outputs the generic safe note "This time respects local working norms." And no locale-specific rationale is included And the safe_mode decision is recorded with the missing/low-confidence signals
Audit Logging and DPA Record-Keeping
Given any polite note generation event When persisting audit data Then an immutable audit record is written with event_id, UTC timestamp, actor_id/service, organizer_region, invitee_region, policy_preset_id, rules_triggered[], redactions_count, safe_mode flag, and hashed token samples And the processing purpose and legal basis metadata are stored for DPA/record-keeping And PII appears only as salted hashes and is not recoverable And 99.9th percentile log write latency is <200ms and records are retained per region policy
Admin Customization & Tone Controls
"As a team lead, I want to tailor the tone and which factors are mentioned so that the notes match our brand and audience expectations."
Description

Offers org- and team-level settings to enable/disable Polite Context, choose tone (formal, friendly, concise), and select which consideration types are allowed (work hours, holidays, lunch, prayer times where permitted by policy). Supports per-audience presets, character limits, and brand lexicon controls. Includes template override UI with versioning, approval workflow, change history, and instant rollout via remote config. Provides sandbox preview and safe-guard checks before publishing changes.

Acceptance Criteria
Org and Team Toggles for Polite Context
Given I am an Org Admin with edit permissions, When I set Polite Context = Off at the Org scope and save, Then all Teams inherit Off within 10 seconds, team toggles display Off (Org override), and generated invites/links include no Polite Context text. Given Org scope = On, When a Team Admin sets their Team scope = Off and saves, Then only that Team's invites/links omit Polite Context; other Teams remain unchanged. Given Org scope = On, When a new Team is created, Then the Team default is On unless the Org policy template specifies otherwise. Given I am not an Admin, When I view the settings page, Then toggles are read-only and the effective state (On/Off and source scope) is visible.
Tone Selection and Localization
Given Org tone = Formal, When any user creates an invite, Then the Polite Context uses the Formal template, localized to the recipient's language/region. Given Team tone override = Friendly, When a Team member creates an invite, Then the Friendly template is used for that Team regardless of Org tone. Given a recipient locale has no localized template, When an invite is generated, Then the English template is used as fallback and a localization-miss metric is recorded. Given a public smart link is opened by viewers with different browser locales, When the landing page renders Polite Context, Then the tone is constant per preset and the copy is localized per viewer locale.
Consideration Allowlist Policy Enforcement
Given Org allowlist = [work hours, holidays], When Polite Context is generated, Then only work hours and holidays are referenced; lunch and prayer times are not mentioned. Given Org allowlist includes prayer times and the region supports it, When the scheduled time overlaps a prayer period, Then a respectful note about prayer time is included in supported locales. Given a Team attempts to enable a consideration not in the Org allowlist, When saving Team settings, Then the save is blocked, a tooltip explains the Org policy, and the effective allowlist is displayed. Given there are no applicable considerations for a meeting, When Polite Context is generated, Then no filler text is added and the message length is 0 or the section is omitted.
Audience Presets, Char Limits, and Brand Lexicon
Given an Audience preset "Candidates" with tone=Friendly, charLimit=280, lexicon rules [replace "meeting"->"chat", ban "ASAP"], When a user selects audience=Candidates, Then the generated text is <=280 characters, uses "chat" in place of "meeting", and contains no "ASAP". Given the raw generated text exceeds the charLimit, When previewing, Then the system truncates using whole-word boundaries and preserves key consideration phrases, displaying the remaining character count. Given no audience preset is selected, When generating, Then the "General" default preset is applied. Given a lexicon replacement conflicts with the template variable output, When generating, Then the lexicon rule takes precedence and the substitution is recorded in the change history with timestamp and actor.
Template Overrides, Versioning, and Approvals
Given a Team Admin edits a template and saves Draft vN+1, When they view versions, Then vN+1 is listed as Draft with author, timestamp, and diff vs vN. Given Org approval required, When the Team Admin submits vN+1 for approval, Then Org Approvers receive a notification and can Approve or Request Changes with a required comment. Given an Approver approves vN+1, When the Publisher clicks Publish, Then vN+1 becomes Live for the selected scope, vN is Archived, and the effective version updates without service interruption. Given vN+1 is pending approval, When invites are generated, Then the Live version remains in use and the Draft has no effect.
Change History and Rollback
Given multiple published versions exist, When viewing Change History, Then each entry shows version, scope (Org/Team), author, approver, action (Draft/Approve/Publish/Rollback), timestamp, and a diff. Given Org Admin selects a prior version and clicks Rollback, When confirmed, Then that version becomes Live immediately and a new history entry records the rollback with reason. Given a Team Admin attempts to roll back an Org-scoped template, When they click Rollback, Then the action is disabled with a permission error and no change occurs.
Sandbox Preview, Safeguards, and Remote Config Rollout
Given a Draft template exists, When Sandbox Preview is opened, Then previews render for at least three sample locales, all supported tones, and selected audiences, showing computed character counts and consideration flags. Given safeguards are enabled (PII scan, banned lexicon, variable validation), When running checks, Then the Publish button remains disabled until all checks pass or an Org Admin overrides with a required reason. Given the template references an undefined variable, When Preview runs, Then the variable is highlighted with an error and Publish is blocked. Given an approved version is Published, When rollout occurs, Then remote config propagates within 60 seconds, caches are invalidated, and new invites/links use the new copy with 0 downtime.
Sender Preview & Inline Edit
"As a coordinator scheduling a cross-region call, I want to preview and lightly edit the message so that I can fine-tune phrasing without risking privacy or inconsistency."
Description

Adds a preview panel in the scheduling flow showing the exact polite note that recipients will see for selected times, with live updates as times change. Allows constrained, inline edits (e.g., swapping synonyms) while preserving policy compliance via real-time linting and hard stops on disallowed content. Supports per-recipient previews for multi-time-zone meetings, mobile responsiveness, and accessibility standards. Provides a reason breakdown tooltip to explain the generated note for user confidence without exposing sensitive inputs.

Acceptance Criteria
Live Preview Refresh on Time Selection
Given I am in the scheduling flow with a selected time window, when I adjust the meeting start/end time, date, or time zone, then the polite note preview refreshes to reflect the change within 500 ms at p95. Given attendees or their locales change, when I update the invitee list or primary recipient, then the preview regenerates in the appropriate language and cultural context without exposing personal details. Given the preview is visible, when I click Send or Share Smart Link, then the final note sent exactly matches the content shown in the preview at that moment (byte-for-byte), and a snapshot is stored for verification. Given the preview refreshes, then transient change highlighting is shown for 2 seconds without causing layout shift > 0.1 CLS.
Constrained Inline Edits with Real-Time Linting
Given the generated note contains editable tokens, when I focus an editable token, then only an approved list of synonyms appears via dropdown; freeform typing, pasting, or adding new text is blocked. Given I select a synonym, when the note updates, then the linting engine validates the content within 300 ms p95 and confirms compliance without altering protected sections. Given disallowed content is introduced (e.g., banned terms detected), when linting runs, then Save/Send/Share are disabled, the offending token is highlighted, and a clear corrective message is displayed with at least one compliant alternative. Given I undo or redo an edit, when I use keyboard shortcuts or UI controls, then the note reverts/applies the change and remains within compliance constraints. Rule: Maximum number of editable tokens per note is enforced (<= 6), and total note length remains within 280 characters unless localization needs require up to 340 characters.
Per-Recipient Localized Preview
Given a meeting has multiple recipients in different locales, when I select a recipient from the recipient switcher, then the preview displays the note localized to that recipient's language and regional customs. Given the recipient switcher is opened, when the list renders, then it supports at least 20 recipients with search-as-you-type and indicates which previews differ (badge: “Varies”). Given a default state, when no recipient is explicitly selected, then the preview shows the primary recipient’s localization. Given I cycle through recipients, when I return to a previously viewed recipient, then the preview shows the last valid, linted version without re-fetch delay > 200 ms (cached).
Reason Breakdown Tooltip
Given a preview is visible, when I hover, focus, or tap the “Why this note?” icon, then a tooltip opens within 150 ms showing 1–3 reasons (e.g., working hours policy, local holiday, focus block) and their non-sensitive sources. Rule: Tooltip content must not include personal calendar titles, exact private hours, or names; only policy categories and generalized time ranges are permitted. Given keyboard navigation, when the icon receives focus and Enter/Space is pressed, then the tooltip opens and is focus-trapped until dismissed; Esc closes it. Given data is insufficient, when a reason cannot be determined, then the tooltip displays “Based on default policy” rather than exposing raw inputs. Rule: Copy-to-clipboard is disabled within the tooltip, while standard screen capture is not actively blocked.
Mobile-Responsive Preview and Editor
Given a mobile viewport (320–480 px width), when viewing the scheduling flow, then the preview renders as a single-column card with no horizontal scroll and tappable edit tokens sized at least 44×44 px. Given a tablet viewport (768–1024 px width), when viewing, then the preview and inline editor stack or sit side-by-side without overlapping, maintaining a minimum 16 px text size. Given per-recipient switching on mobile, when invoked, then a dropdown replaces tabs and selection changes update the preview within 600 ms p95. Rule: Interaction latency for taps is < 100 ms, transition animations are <= 200 ms, and content reflow maintains layout shift <= 0.1 CLS across breakpoints.
Accessibility Compliance (WCAG 2.1 AA)
Given keyboard-only use, when navigating the preview, editor tokens, and tooltip, then all elements are reachable in a logical tab order; Enter/Space activates controls; Esc dismisses popovers. Rule: Preview region uses role=region with aria-label="Polite note preview"; editable tokens expose aria-haspopup="listbox" and proper labelling; tooltip uses role=tooltip; dynamic preview updates announce via aria-live="polite" without repetitive announcements. Rule: Text and interactive elements meet color contrast ratio >= 4.5:1; zoom to 200% preserves functionality and content without loss. Given validation errors, when they occur, then messages are exposed to assistive tech via aria-live="assertive" and include programmatic associations to the offending token. Rule: Implementation complies with WCAG 2.1 AA success criteria including 1.3.1, 1.4.3, 2.1.1, 2.4.3, 3.3.1, and 3.3.3.
Failure Handling & Guardrails
Given the linting service times out (> 2 s) or errors, when attempting to edit or send, then a non-dismissible error banner appears, Send/Share are disabled, and a Retry action is provided; errors are logged with correlation IDs. Given the note generation service fails, when a compliant fallback template is available, then the fallback is displayed and used; otherwise, Send/Share remain disabled until a compliant note is available. Given offline conditions, when connectivity is lost, then the last valid preview remains visible and labeled “Offline”; edits are blocked; autosave persists the last user selections locally for at least 10 minutes and re-syncs on reconnect. Given the user proceeds to Send/Share, when the action completes, then the note snapshot used is identical to what was displayed at commit time and is persisted for audit with a content hash.
Impact Analytics & A/B Testing
"As a product owner, I want to quantify how polite context impacts bookings so that we can iterate confidently and prove ROI."
Description

Measures the effect of Polite Context on booking outcomes, including acceptance rate, time-to-book, and reschedule rate, with regional and language segmentation. Supports A/B and multivariate tests across templates, tones, and inclusion of specific consideration types. Provides dashboards, CSV export, and privacy-safe event instrumentation, while controlling for confounders (e.g., sender, segment, time-of-day). Exposes experiment toggles in admin and integrates with existing analytics pipelines.

Acceptance Criteria
Dashboard Core Outcome Metrics
Given a workspace with invites and bookings in the selected date range When a user views the Impact Analytics dashboard without filters Then the dashboard shows Acceptance Rate, Median Time-to-Book, and Reschedule Rate with the following definitions: - Acceptance Rate = confirmed bookings / unique invites sent (excluding bounces and cancellations) - Median Time-to-Book = median minutes from invite/link sent to booking confirmation - Reschedule Rate = bookings with ≥1 reschedule within 14 days / confirmed bookings And each KPI reveals numerator and denominator on hover And a data freshness indicator shows last update time and is ≤15 minutes old p95 And KPI values match an audit recomputation on a sampled dataset within ±0.1 percentage points
Regional and Language Segmentation
Given events include locale and region codes derived without exposing personal details When the user applies breakdowns by Region and Language Then per-segment KPIs render using ISO 3166-1 region codes and IETF language tags And segments with <30 invites in the selected date range display "Insufficient data" and are excluded from totals And region and language filters stack with sender, segment, time-of-day, meeting length, and day-of-week And segmented counts equal the sum of visible segments and match the overall within rounding tolerance
Experiment Setup and Randomization
Given an admin defines an experiment with 2–5 variants, variant definitions (template/tone/consideration types), eligibility filters, and allocation percentages summing to 100% When the experiment is activated Then eligible invites are assigned using stratified randomization by sender and segment with deterministic assignment keys for idempotency And concurrent experiments use independent namespaces to avoid cross-experiment interference And a power calculator displays required sample size for detecting a 5% absolute acceptance-rate lift at α=0.05 and power=0.8 And activation is blocked if any variant fails validation or if allocations ≠100%
Experiment Results with Adjusted Lift and Confounder Controls
Given an active or completed experiment with sufficient data When a user opens the experiment report Then the report shows per-variant Acceptance Rate, Time-to-Book (median), Reschedule Rate, raw lift vs control, and 95% confidence intervals And adjusted lift is computed using regression that controls for sender, segment, time-of-day, region, and language And the report flags underpowered results and applies multiple-testing correction when >2 variants And assignment balance checks by confounder are displayed and must pass (p>0.1) or show warnings And the Results CSV export includes variant assignment and confounder columns for replication
Admin Experiment Toggles and Safe Rollouts
Given an experiment in Draft state When an admin sets rollout to X% (1–100) and clicks Start Then the traffic split updates within 5 minutes and is audit-logged with user, timestamp, and change summary And Pause stops new assignments immediately while continuing to track outcomes for already-assigned invites And Stop finalizes the experiment, prevents reactivation, and preserves read-only results And admins can schedule phased rollouts (e.g., 10%→50%→100%) at specified times And Preview renders each variant note in a selected locale without sending any invite
Privacy-Safe Instrumentation and Pipeline Integration
Given invite, booking, reschedule, and experiment-assignment events occur When events are recorded and exported to the analytics pipeline Then no personal names, email addresses, or free-text content are stored; participant and sender IDs are hashed; IP addresses are discarded; only template/variant IDs, locale/region codes, timestamps, and assignment IDs are included And events adhere to a documented JSON schema with semantic versioning; unknown or unsafe fields are rejected And delivery guarantees idempotency via event_id and supports streaming (p95 end-to-end <2 minutes) and daily batch with retries; drop rate <0.1% per day And data retention defaults to 180 days and is configurable per workspace And schema and PII lint checks run in CI and block deployment on violations
CSV Export with Filters and Data Consistency
Given a user applies filters (sender, segment, time-of-day, region, language, template, meeting length, day-of-week) and selects a date range When the user exports CSV Then the file contains columns for hashed ids, timestamps (UTC ISO8601), metrics flags, variant assignment, and confounder fields that align with on-screen filters And row counts and KPI aggregations computed from the CSV match the dashboard under the same filters And files up to 1,000,000 rows download within 30 seconds p95; larger exports require pagination tokens and return multiple files And CSV excludes PII, passes a schema validator, and includes a header with column names and types And the export action is audit-logged with user, workspace, filter set, and row count

Why Sensitive

Click any flagged window to see the specific cultural reason—e.g., Maghrib prayer, school pick‑up, regional siesta—and receive instant alternates that resolve the issue. Educates bookers in the moment and prevents repeat mistakes. Clear explanations keep negotiations respectful and fast.

Requirements

Cultural Reason Detection & Flagging Engine
"As a remote team lead booking cross-region meetings, I want the system to detect and flag culturally sensitive windows with clear reasons so that I avoid proposing inappropriate times for attendees."
Description

Back-end service that evaluates proposed meeting windows against participant locales, time zones, organizational policies, and known cultural calendars to identify and flag sensitive times. Consumes sources such as public holiday feeds, regional siesta schedules, prayer time computations by location, and user-defined focus/blocked blocks. Outputs standardized reason codes, human-readable labels, affected participants, and severity. Integrates with the timeline, availability API, and smart links to surface flags in real time across multi-participant scenarios. Supports caching and freshness windows, DST handling, and per-user overrides while preserving privacy by avoiding exposure of personal religious or health data.

Acceptance Criteria
Standardized Multi-Source Flag Output
Given a proposed meeting window and participants with distinct locales, time zones, and org policies When the engine evaluates the window against holiday feeds, regional siesta schedules, prayer time computations, and user-defined focus/blocked blocks Then for each conflict it returns a flag object containing non-null fields: reasonCode (snake_case), reasonLabel (<=60 chars), severity in [block, discourage, info], source in [holiday, prayer, siesta, focus, policy], affectedParticipants (>=1 participantId), timeRangeLocal per affected participant (ISO 8601 with zone), and freshness {ageMs, maxAgeMs} And overlapping conflicts with the same reasonCode and participant are merged into a single timeRange per participant And the response includes a top-level evaluationWindow in both UTC and per-participant local time
Privacy-Preserving Reason Exposure Controls
Given a conflict derived from a sensitive category (e.g., prayer) affecting one or more participants When the request scope is external (surface=shared_link or timeline_public) Then flags omit participant identifiers and instead include affectedCount and privacyLevel='sensitive'; reasonLabel is generic (e.g., 'Local prayer time') and contains no religious/health identifiers; no user-specific fields are present And when the request scope is internal with role=scheduler_owner and participant sharingPreference=explicit_opt_in Then flags include participantIds but exclude any religious affiliation or health details; privacyLevel and visibility fields are set accordingly
Caching and Freshness Compliance
Given per-source maxAgeMs defaults (holiday=259200000, prayer=21600000, siesta=604800000, focus=300000) and a warmed cache When the same window and participants are evaluated repeatedly within each source's maxAgeMs Then the engine serves cached results with cacheHit=true and freshness.ageMs <= maxAgeMs for every source And when a source's maxAgeMs expires or a source emits an update event Then the next evaluation recomputes that source (cacheHit=false) and returns freshness.ageMs < 1000 ms for that source while retaining valid caches for others And no response contains freshness.ageMs > maxAgeMs
Correctness Across DST Transitions
Given a participant in America/New_York on the spring-forward date and a window spanning 01:55–03:10 local time When the engine evaluates conflicts and converts times Then it omits the nonexistent 02:00–02:59 interval, returns continuous real minutes only, and produces correct UTC normalization And given a participant in Europe/Berlin on the fall-back date with a window spanning the repeated hour Then the engine evaluates both instances of the repeated hour, merges duplicate flags appropriately, and produces no ±60 minute or ±1 minute errors
Graceful Degradation on Source Outage
Given the holiday feed returns HTTP 5xx and the prayer source responds with data older than maxAgeMs When the engine evaluates a window Then the response indicates overallStatus='partial', includes dataSourceStatus.holiday='degraded' and prayer='stale', and returns available flags from healthy sources And the engine enforces a per-source timeout <= 1000 ms, does not throw, and returns HTTP 206 for API consumers while still producing a valid flags array And an audit event with type='data_source_degraded' is emitted with correlationId
Per-User Overrides and Severity Adjustment
Given a participant has an active override that allows meetings during their usual sensitive time (overrideId, scope, expiry) When the engine evaluates a window overlapping that sensitive time Then the flag for that participant is suppressed or its severity is downgraded according to override rules, while other participants' flags remain unaffected And the returned flag(s) include overrideRef metadata and do not disclose the override's personal rationale And when the override expires, subsequent evaluations no longer apply the suppression/downgrade
Real-Time Performance for Multi-Participant Evaluation
Given a proposed window of 60 minutes and 12 participants across at least 8 time zones When the engine evaluates flags under steady load of 50 RPS with burst up to 200 RPS Then end-to-end latency is <= 250 ms p95 and <= 500 ms p99, and the flags array is complete and schema-valid And CPU utilization remains below 70% and memory growth is bounded (no leak > 5% over 1 hour) And no more than 1 external source request per source per participant per minute is issued due to caching and request coalescing
Click-to-Explain Panel
"As a booker, I want to click a flagged time window and immediately understand why it’s sensitive and who is affected so that I can make a fast, respectful scheduling decision."
Description

Interactive UI panel activated by clicking any flagged window on timelines and booking links. Displays a concise, plain-language explanation of the sensitivity, which attendees are impacted, the rule source (policy, calendar, computation), and the time range affected. Provides guidance on how to proceed, including quick actions to view alternates or adjust constraints. Designed for sub-200 ms open time, keyboard navigation, screen-reader labels, and responsive layouts. Logs interactions for analytics without storing sensitive content in the client.

Acceptance Criteria
Open Panel via Click or Keyboard
Given a flagged time window is visible on a timeline or booking link And the user has pointer focus or keyboard focus on that flagged window When the user clicks it or presses Enter or Space Then the Explain Panel opens within 200 ms And it displays: a plain-language reason, impacted attendees, rule source (policy|calendar|computation), and the affected time range And it shows actionable buttons: "View alternates" and "Adjust constraints" And the panel can be dismissed via Close button, Escape key, or clicking outside
Performance SLA for Explain Panel
Given a mid‑tier device and network (mobile 4G, ~50 ms RTT) in a production‑like environment When the Explain Panel is opened from a flagged window Then time‑to‑visible <= 150 ms (p50) and <= 300 ms (p95) And time‑to‑interactive <= 200 ms (p50) and <= 350 ms (p95) And input responsiveness during open has First Input Delay <= 50 ms (p95)
Keyboard Accessibility and Focus Management
Given any flagged window is focused When the user opens the Explain Panel via keyboard Then focus moves to the panel header or first interactive element And focus is trapped within the panel until it is closed And Tab/Shift+Tab order follows visual order and reaches all interactive elements And pressing Escape closes the panel and returns focus to the original trigger And all actions are operable via keyboard only
Screen Reader Semantics and Labels
Given a screen reader is active (e.g., NVDA, VoiceOver) When the Explain Panel opens Then it is exposed as a modal dialog with an accessible name "Why this time is sensitive" And the reason text is announced with an associated label And impacted attendees are announced with names and time zone context And all controls have accessible names matching visible labels (e.g., "View alternates", "Adjust constraints", "Close") And no WCAG 2.2 AA violations are detected for role/name/description on panel elements
Responsive Layout and Usability
Given viewport widths of 320 px, 768 px, 1024 px, and 1440 px When the Explain Panel opens Then no horizontal scrolling is required And body text is at least 14 px and remains readable And primary actions are visible without scrolling at 320 px And interactive targets are at least 44x44 px And no critical content is clipped or overlapped across breakpoints
Quick Actions: Alternates and Adjust Constraints
Given the Explain Panel is open for a specific flagged window When the user selects "View alternates" Then at least 3 alternate time slots within the next 14 days are displayed within 500 ms And none of the alternates overlap the same sensitivity or violate participants’ work hours, holidays, or focus blocks And choosing an alternate pre‑fills the scheduling flow with that slot When the user selects "Adjust constraints" Then the constraint editor opens pre‑filtered to the affected participants and date range from the flagged window
Analytics Logging and Privacy Safeguards
Rule: The client logs only event metadata {event_type, trigger_id, rule_type, timestamp, anonymized_user_id} for panel open/close and action clicks Rule: Human‑readable reason text and attendee names are never included in analytics payloads nor persisted in client storage Rule: Events are sent only when analytics consent is true; otherwise, events are suppressed Rule: Analytics requests use HTTPS; payload size <= 2 KB; retries use exponential backoff with a 24‑hour cap Rule: Automated tests verify absence of reason text and attendee names in emitted payloads
Instant Alternate Time Suggestions
"As a booker, I want instant alternative time options that resolve the sensitivity while honoring everyone’s constraints so that I can reschedule without trial-and-error."
Description

Suggestion engine that generates conflict-free alternate meeting slots when a flagged window is selected. Respects all participants’ work hours, focus blocks, holidays, fairness rotation rules, and organizer constraints like meeting length and buffer times. Returns a small ordered set of options with rationale (e.g., avoids Maghrib by 45 minutes, within all work hours) and supports one-click replacement in the invite or link. Handles partial attendee acceptance, time zone normalization, and deduplication across calendars. Exposes service endpoints for both UI and API consumers.

Acceptance Criteria
Flagged Window Generates Ordered Alternates
Given a flagged window is clicked for a meeting with up to 6 attendees and a configured search horizon of 14 days When the suggestion engine generates alternates Then it returns 3–5 unique time slots that are conflict‑free for all attendees and entirely within their work hours And each slot observes attendees’ focus blocks and holidays and avoids the flagged cultural window by at least 30 minutes And options are strictly ordered best‑first by proximity to the requested time with fairness‑rule tie‑breakers And each option includes a rationale with at least two points (e.g., “avoids <reason> by <minutes>”, “within all work hours”) And the response is delivered within 1500 ms at p95 for up to 6 attendees over a 14‑day horizon
Organizer Constraints: Duration and Buffers Respected
Given the organizer specifies meeting length L minutes and buffer times of Bpre and Bpost minutes When alternates are generated Then every suggestion has duration = L And on the organizer’s calendar there is at least Bpre free minutes immediately before and Bpost free minutes immediately after the suggested slot And suggestions align to the organizer’s time rounding increment (5/10/15 minutes) And no suggestion violates any attendee’s declared hard buffers
One‑Click Invite Replacement
Given suggestions are displayed for a flagged window When the organizer selects “Replace with this time” Then the existing event is updated to the selected slot with the same UID, attendees, title, description, and conferencing details And attendees receive an update notification And the shared scheduling link reflects the new time And the operation completes within 3 seconds at p95 And on failure the system shows an actionable error and does not commit partial updates
Time Zone Normalization and Fairness Rotation
Given attendees span at least two time zones and a fairness rotation state exists for the meeting series When alternates are generated Then each suggestion is stored in UTC and rendered correctly in each attendee’s local time with a valid TZID in ICS payloads And the top‑ranked suggestion does not assign the earliest local start to the same region as the previous scheduled occurrence when an equally valid alternative exists And each suggestion’s rationale includes an explicit fairness note (e.g., “rotates early start to US this time”)
Partial Acceptance Impact and Labeling
Given an event with Y attendees where X (X ≥ 1) have accepted the current time When alternates are generated Then at least one suggestion keeps all X accepted attendees available if such a slot exists And each suggestion includes a label “keeps X of Y accepted” reflecting maintained availability for previously accepted attendees And suggestions are ordered to favor higher kept‑accepted counts when other factors tie
Calendar Deduplication Across Accounts
Given an attendee has multiple connected calendars with duplicate or mirrored events When alternates are generated Then the engine treats the attendee as a single principal and does not create false conflicts from duplicate events And no suggestion is rejected solely due to duplicated entries across the same attendee’s calendars And conflicts are still detected when duplicates represent distinct events (different UIDs/time ranges)
API Endpoint: Suggestions Service
Given an authenticated client calls POST /v1/suggestions with organizer, attendees (emails and time zones), meeting length, buffer settings, flaggedWindowId, and constraints When the request is valid Then the service returns 200 with 3–5 suggestions each containing id, start/end in UTC, per‑attendee local time previews, rationale[], keepsAcceptedCount/total, and fairnessApplied flag And p95 latency is ≤ 1000 ms for up to 6 attendees and a 14‑day search horizon And invalid input returns 400/422 with error codes; missing/invalid auth returns 401; unknown flaggedWindowId returns 404; rate‑limited requests return 429; server errors return 500 And results are idempotent when an Idempotency‑Key header is provided And all suggestion ids are unique within the response
Respectful, Localized Explanations
"As an invitee, I want explanations presented in respectful, localized language so that my cultural boundaries are understood without me having to justify them."
Description

Content templating and localization system that renders culturally respectful, jargon-free explanations in the user’s language and regional formats. Includes tone guidelines, glossary tooling, RTL support, and fallback phrasing when specifics cannot be disclosed. Maps reason codes to localized names (e.g., Maghrib prayer), dates/times, and links to learn more where appropriate. Ensures privacy by generalizing sensitive personal contexts and honoring org redaction settings.

Acceptance Criteria
Localized Rendering in User Language and Regional Formats
Given the user's app locale is supported and the reason_code has a locale entry When the user clicks a flagged window Then the explanation text, reason name, dates, and times render in the user's language and CLDR regional formats for that locale And the displayed time zone label matches the viewer's zone And punctuation, list separators, and number formats conform to the locale's standards
Tone and Jargon Compliance
Given the explanation template and substitutions When the message is generated Then it contains no terms from the tone_jargon_blocklist v1 and no internal codes or unexplained acronyms And the phrasing is neutral and respectful per tone guidelines And the readability score meets the configured threshold (e.g., FKGL <= 8 for English locales) And the message length is <= 300 characters unless a learn-more link is present
Reason Code Mapping and Learn-More Links
Given a reason_code with a mapping entry and an org policy that allows learn-more links When the user opens the explanation Then the localized display_name for the reason is shown instead of the raw code And the learn-more link uses the locale-specific URL and returns HTTP 200 And if org policy forbids links or the reason is marked as not publicly explainable, the link is not rendered and no placeholder remains
Privacy Redaction and Generalization
Given org redaction setting = "generalize_personal" When the underlying reason category is PersonalSensitive Then the explanation uses an approved generalized phrase (e.g., "personal commitment") without names, locations, or medical/HR specifics And API responses and client logs exclude any free-text details; only reason_code, category, and locale_id are present And the same generalized text is shown to both internal and external viewers under this setting
Fallback Phrasing on Missing Specifics
Given the localized name or detail cannot be resolved due to missing translation, removed glossary term, or redaction When rendering the explanation Then a locale-appropriate fallback phrase is used that explains the conflict without specifics and contains no unresolved tokens (e.g., "{term}") And at least one alternate meeting window is suggested within the configured horizon that fits both parties' working hours, rendered in the viewer's locale and time zone
RTL Language Support
Given the user's locale is RTL (e.g., ar, he) When the explanation is opened Then the container sets dir="rtl" and unicode-bidi: isolate; punctuation and bullets are mirrored appropriately; numbers format per the locale And mixed LTR substrings (e.g., URLs) are isolated with dir="ltr" to prevent glyph reordering And no text truncation or overflow occurs at typical mobile widths (<= 375px)
Glossary Term Injection and Tooling
Given the template references a glossary token {term:siesta} and a locale-specific glossary entry exists When rendering Then the token resolves to the localized term with an accessible clarification (tooltip on desktop, inline note on touch) in the same locale And if a translation for the term is missing, the term falls back to the source-locale term with a generic explanation, and a translation-missing event is logged And glossary expansion never reveals sensitive personal details; terms flagged sensitive are suppressed and replaced by generalized phrasing
Admin Sensitivity Policies & Overrides
"As a workspace admin, I want to tailor sensitivity categories and enforcement rules so that the feature aligns with our company culture, compliance, and inclusivity goals."
Description

Administrative controls to configure which sensitivity categories apply, their strictness levels, and scope by team, role, or region. Supports custom blocked windows, policy-based overrides, computation method selection for prayer times, and data source management for holidays. Provides audit logs, versioning, and a change-preview mode to see how policies affect typical schedules. Exposes SCIM/SSO-based provisioning for policy assignment and GDPR-compliant data handling options.

Acceptance Criteria
Configure Sensitivity Categories and Scope
Given I am an Org Admin with policy permissions When I create a new sensitivity policy named "Global Remote" with categories [Prayer Times, School Pick-up, Siesta] And I set strictness as Prayer Times=Hard Block, School Pick-up=Soft Warn, Siesta=Info Only And I scope the policy to Teams=[EMEA Sales], Roles=[Manager], Regions=[MENA, LATAM] And I publish the policy Then the system persists the policy as version 1 with a unique ID And users matching the scope receive the policy within 60 seconds And users outside the scope are unaffected
Custom Blocked Windows Enforcement
Given a published policy scoped to Region=ES with strictness Hard Block for custom windows And a custom block "School pickup" every weekday 15:00–16:30 Europe/Madrid is configured When a booker tries to schedule with a scoped attendee during that window via a smart link Then the time slot is not selectable And the Why Sensitive panel labels the conflict "School pickup" with the configured description And the system proposes at least 3 alternate slots within the next 7 days that do not intersect any blocked window
Policy-Based Override Workflow
Given a policy with Hard Block on Prayer Times and override rule Requires Approval with Approver Group=Team Leads And a meeting attempt conflicts with Maghrib for a scoped attendee When a booker with role Director submits an override request including a reason Then the approver receives a notification and the request is visible in the Admin > Overrides queue When the approver approves the request Then the slot becomes schedulable only for that meeting instance And the event is marked "Override" in audit trails and attendee notifications include the override note
Prayer Time Computation Method Selection
Given a policy scoped to Region=MENA with prayer-time source "Rituals API" When I set computation method to "Umm al-Qura" and location basis to "Attendee Local City" And I open Change Preview for the next 7 days for Riyadh Then the preview shows prayer windows matching the API's "Umm al-Qura" output within ±1 minute And the delta view highlights any shifts from the previous method And after publishing, new bookings use the updated windows
Holiday Data Sources and Fallback
Given a policy with holiday sources Primary="Workday Holidays", Secondary="Google Public Holidays" And region mapping DE=Germany, US=United States When I open Change Preview for a DE attendee week containing Tag der Deutschen Einheit Then the preview flags the holiday window with explanation from the Primary source When the Primary source is unreachable Then the system uses the Secondary source for that region and logs a warning to the audit stream And publishing does not fail due to Primary source outage
Audit Logs, Versioning, Preview, and Rollback
Given an existing policy at version 3 When I change strictness for Siesta from Info Only to Soft Warn and add a change note "LATAM feedback" And I open Change Preview for personas [EMEA Manager, LATAM IC] Then the preview shows before/after bookable-hour counts per persona and a list of affected time windows And publishing creates version 4 with editor, timestamp, diff summary, and note recorded in the audit log When I rollback to version 3 Then version 5 is created restoring version 3 settings and the rollback is logged with reason
Provisioning via SCIM/SSO and GDPR Controls
Given SCIM is connected and SSO is enabled with attribute mapping role=department, region=country When a SCIM group "EMEA-Sales-Managers" is mapped to policy "Global Remote" Then new members of that group are assigned the policy within 60 seconds and removals revoke it within 60 seconds And manual assignment is blocked when assignment is managed by SCIM and shows an explanatory message When an admin sets data residency=EU and retention=180 days and requests a user data export and deletion for a subject Then exports include policy-assignment records and audit entries only, with no sensitive content of meeting bodies And deletion purges the subject's identifiers from policy assignments and logs within 30 days while retaining aggregated, anonymized metrics
Telemetry, Feedback, and Tuning
"As a product owner, I want insight into how reasons and alternates perform so that we can tune accuracy and reduce friction over time."
Description

Privacy-preserving telemetry capturing flag views, explanation opens, alternate suggestion acceptance, and dismiss reasons to measure effectiveness. In-UI feedback (“Not relevant”, “Already acceptable”) trains heuristics to reduce false positives and improve ranking of alternates. Includes dashboards and export for PMs and ops, retention controls, and automated alerts for data drift (e.g., holiday feed failures).

Acceptance Criteria
Flag and Explanation Telemetry Capture
Given a user clicks a flagged time window in the Timeglue timeline and opens the Why Sensitive panel When the panel renders and the user expands the cultural explanation Then a flag_view event is recorded with fields: event_type, timestamp_utc, session_id (UUIDv4), user_scope (anonymous|authenticated), org_hash, flag_id, region_code, timezone, client_version, feature_version, ui_surface, and contains no PII And an explanation_open event is recorded with fields: flag_id, reason_code, locale, explanation_variant, timestamp_utc, session_id, org_hash, client_version, and contains no free-text body content And both events are delivered to the telemetry backend within P95 ≤ 1s and P99 ≤ 5s (with offline queue + retry) And duplicate flag_view events within 30s are deduplicated by (session_id, flag_id) And org/user telemetry opt-out suppresses both events
Alternate Suggestion Acceptance Tracking
Given alternates are shown to resolve a sensitive window When the user selects an alternate and confirms scheduling Then an alternate_accept event is recorded with fields: suggestion_id, suggestion_rank, algorithm_version, flag_id, booking_id, timestamp_utc, session_id, org_hash, client_version, and contains no invitee PII And exactly one alternate_accept event is emitted per booking confirmation, idempotent by (booking_id, suggestion_id) And the event is delivered within P95 ≤ 1s and P99 ≤ 5s (with retry)
Dismiss Reason Capture and Schema
Given a user dismisses a flag or alternate When the dismiss UI is presented Then the UI offers buttons: Not relevant, Already acceptable, Other (with optional free-text up to 140 chars) And selecting any option records a dismiss_reason event with fields: subject_type (flag|suggestion), subject_id, reason_code, timestamp_utc, session_id, org_hash, client_version, free_text_present (boolean), free_text_hash (salted), and stores no raw free text when PII patterns (email/phone) are detected And only one dismiss_reason is stored per (session_id, subject_id) And the control is fully keyboard accessible and labeled for screen readers
In-UI Feedback Training Ingestion
Given dismiss_reason and related feedback events exist in telemetry When the nightly ingestion job runs Then events are written to the feedback_training store with schema_version, partitioned by event_date, and validated against a contract And differential privacy noise (epsilon ≤ 1.0) is applied to published aggregate counts by segment And the next heuristic/model build consumes the store behind a feature flag and writes model_versioned artifacts And rollout is canaried to ≤10% of traffic until evaluation metrics (precision/recall no worse than −5% vs. prior) are met
PM Dashboards and Export
Given a PM with analytics permissions opens the Telemetry dashboard When a date range and filters (org_tier, region, client_version) are applied Then the dashboard displays time series and funnels for flag_view → explanation_open → alternate_accept, plus distribution of dismiss_reason And all visuals render within 30s P95 and exclude PII And the filtered dataset can be exported as CSV with a downloadable signed URL (expires ≤ 1 hour) and includes a schema dictionary And drill-down shows only redacted identifiers (hashes) and never raw free text
Retention Controls and Data Deletion
Given an org admin configures event retention per event type When the daily retention job executes Then events older than the configured TTL (30–365 days) are purged and an immutable audit log entry is written And a right-to-erasure request for a subject user_hash purges associated telemetry within 7 days, including secondary indexes, with an audit record And backup snapshots are pruned within 30 days to remove deleted subjects And data exports exclude records beyond TTL
Automated Data Drift Alerts
Given production telemetry and external feeds (e.g., holiday calendars) are monitored hourly When anomalies are detected (flag_view volume change > 30% day-over-day, alternate_accept rate change > 25% week-over-week, or zero holiday feed updates for any region over 24h) Then alerts are sent to on-call via Slack and email with context (metric, baseline, region, client_version) and a runbook link And alert noise is controlled via maintenance windows and a 2-hour cooldown per metric-region And alert acknowledgment and resolution are tracked in an audit log

Product Ideas

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

FairShare Slots

Auto-rotates recurring meetings to spread pain fairly across time zones. Shows a fairness score and suggests next-slot rotations that never break work-hour guardrails.

Idea

Gatekeeper Links

SSO-gated booking links that enforce role-based windows, attendee caps, and per-account quotas. Block outsiders from prime hours while keeping compliant audit trails.

Idea

Heatmap Canvas

Paintable team heatmap revealing safest overlap by hour and region. Drag to carve windows and watch conflict, holiday, and focus-block risk meters update live.

Idea

Surge Windows

One-click surge mode temporarily expands acceptable windows for incidents, launches, or quarter’s end, then auto-sunsets. Requires approval, logs exceptions, and nudges back to normal.

Idea

Zero-Touch Onboarding

Detects your domain and auto-imports work hours, holidays, and teams from HRIS/IdP, proposing sane defaults. New orgs get ready-to-book in minutes.

Idea

Ghost Hours

Share masked availability with external partners—show open/closed without exact times. Ephemeral ICS tokens and per-link calendars prevent scraping and protect contractor privacy.

Idea

Culture Clock

Suggests culturally respectful windows beyond holidays—avoiding prayer times, siestas, and school runs per locale. Flags fragile periods and proposes alternates automatically.

Idea

Press Coverage

Imagined press coverage for this groundbreaking product concept.

P

Timeglue Debuts FairShare to Make Recurring Global Meetings Equitable, Legal, and Drama‑Free

Imagined Press Article

San Francisco, CA — September 1, 2025 — Timeglue, the time zone scheduling platform built for remote team leads at startups and agencies, today announced FairShare, a new suite that distributes the burden of cross‑region meetings fairly while honoring work hours, holidays, and focus time. FairShare ends the silent tax on the same people taking after‑hours calls, replacing ad‑hoc compromises with transparent, defensible rotations that work for humans and comply with local labor rules. FairShare addresses a widespread reality: global teams rely on recurring standups, reviews, and planning sessions, but the cost of inconvenient hours often falls on the same regions and roles. Leaders struggle to balance equity, velocity, and compliance without spending hours in spreadsheets or triggering resentment. FairShare automates the heavy lifting with clear rules, a visible ledger, and smart suggestions that keep meeting windows humane. At the core of FairShare are role‑aware policies and live simulations that show the impact of each cadence before it locks. Role Weights let organizations assign lighter weights to candidates, customers, or protected roles and heavier weights to senior or rotating duty roles. Time Debt tracks cumulative after‑hours minutes at the person level and proposes payback rotations that restore balance. When a planned rotation collides with a holiday or culturally sensitive period, Holiday Swap offers the next fair slot or a cross‑swap that preserves the longer‑term equity curve. Series Balancer coordinates fairness across multiple recurring meetings so the same person isn’t repeatedly penalized across standups, retros, and reviews. FairShare also meets teams where real life happens. Swap Market allows attendees to propose one‑off trades within fairness constraints, with automatic recalculation of debt and credit. Legal Limits encode locale labor rules and rest‑period requirements, blocking suggestions that risk non‑compliance and surfacing safe alternates with a plain‑English explanation of why a slot was ruled out. Before a plan is published, Fairness Simulator projects the next quarter’s rotations and shows equity and legal outcomes by person and region, so leaders can adjust cadence, duration, or weights with confidence. “Fairness isn’t just a value—it’s an operational system,” said Maya Ingram, CEO of Timeglue. “Global collaboration shouldn’t rely on martyrdom or manual spreadsheets. FairShare turns equity into a first‑class constraint alongside time zones, holidays, and focus blocks, so teams can move fast without burning trust.” Early customers report measurable gains in morale and predictability. “We run engineering and design across six time zones,” said Mateo Alvarez, Head of Engineering at NimbusStack. “FairShare made our rotations transparent, cut down on late‑night surprises, and gave us a credible way to repay after‑hours minutes. People trust the plan because the rules are clear and the ledger is visible.” Compliance teams value that fairness never comes at the expense of policy adherence. “The combination of Legal Limits and the Fairness Simulator helps us prove we’re making responsible choices,” said Priya Narayanan, Employment Counsel at Northbridge Labs. “We can show auditors and employees the exact rule that applied to a blocked time and the safe alternates that were considered. It’s equity we can defend.” FairShare fits directly into the way remote leaders already schedule. On Timeglue’s paintable Heatmap Canvas, team leads drag to carve acceptable overlap windows that honor work hours, holidays, and focus blocks. FairShare then layers in equity: it scores each proposed series, suggests the best rotation plan, and publishes it to smart links that prevent after‑hours invites. When a surge week or launch window requires exceptions, FairShare coordinates with Timeglue’s Surge controls to cap exposure, require approvals, and automatically schedule a cooldown that pays back Time Debt. Availability and pricing: FairShare is generally available today for Timeglue Pro and Enterprise plans at no additional cost through Q4 2025. Role Weights, Time Debt, Holiday Swap, Series Balancer, Swap Market, Legal Limits, and Fairness Simulator are included at launch. Admin APIs for fairness dashboards and HRIS exports enter private beta later this month. Existing customers can enable FairShare from Admin Settings. New teams can start a 14‑day trial and import rosters and work‑hour policies in minutes with Timeglue’s Quick Connect. Security, privacy, and transparency are built in. FairShare explanations show the “why” behind each suggestion, and every change is logged alongside who approved it and which rule version applied. Timeglue encrypts data in transit and at rest, supports SSO via Okta and Azure AD, and provides exportable logs to satisfy audits. About Timeglue: Timeglue is time zone scheduling software that finds sane cross‑region meeting windows while honoring work hours, holidays, and focus blocks. Built for remote team leads at startups and agencies, it ends time zone math and back‑and‑forth with a draggable timeline, smart links, and safeguards that prevent after‑hours invites. Customers use Timeglue to align calendars, protect focus time, and scale global operations respectfully. Call to action: To learn more about FairShare or to request a demo, visit timeglue.com/fairshare. A media kit with screenshots and customer stories is available at timeglue.com/press. Media contact: Timeglue Communications press@timeglue.com +1 415 555 0139 www.timeglue.com/press

P

Timeglue Introduces Gatekeeper Links to Protect Prime Hours, Enforce Quotas, and Keep Bookings Audit‑Ready

Imagined Press Article

San Francisco, CA — September 1, 2025 — Timeglue today unveiled Gatekeeper Links, a new layer of role‑aware booking controls that protect prime hours, enforce per‑account quotas, and produce clear audit trails—without adding friction for customers or partners. Designed for revenue teams, agencies, and global operations, Gatekeeper Links turn common scheduling chaos into reliable, policy‑compliant bookings that respect your team’s focus time. Gatekeeper Links operate like smart, SSO‑aware doors. Instead of exposing the same calendar to everyone, Timeglue reveals different windows dynamically based on who is booking and what they’re entitled to. Prime Hour Shield reserves premium time bands for approved roles and account tiers, steering others to off‑peak options so critical work stays unblocked while deep‑work hours remain protected. Tier Sync keeps these entitlements current by ingesting roles, groups, and customer tiers from systems like Okta, Azure AD, and Salesforce, eliminating brittle manual lists. Revenue leaders can finally apply operational guardrails to scheduling. Quota Guard enforces per‑account booking quotas with real‑time counters, soft and hard limits, and configurable grace periods. Cap Bands apply conditional attendee caps by segment, region, or time of day to keep sessions productive and compliant, automatically waitlisting overflow and suggesting smaller‑group alternates. Every booking decision is recorded in the Audit Ledger, a tamper‑evident log showing who booked, why they were allowed or blocked, which rule version applied, and the SSO attributes used. When a legitimate edge case arises, Access Request offers a streamlined override workflow with SLAs, time‑bounded approvals, and automatic exception recording. “Calendars have become an uncontrolled entry point into our teams,” said Dev Patel, VP of Product at Timeglue. “Gatekeeper Links give companies a polite, programmable bouncer—welcoming the right meetings at the right times, and documenting why. You protect prime hours, prevent overcommit, and gain the audit trail you need, all in one flow.” Customer teams are seeing immediate benefits. “Our CS org serves hundreds of customers across three regions,” said Jada Kim, VP of Customer Success at Arcadia Cloud. “With Gatekeeper Links, top‑tier accounts see the premium windows they pay for, while others are smoothly guided to off‑peak slots. Quota Guard ended the month‑end scramble, and the Audit Ledger lets us settle any SLA questions in minutes.” Gatekeeper Links integrate with Timeglue’s core scheduling canvas. Leaders paint acceptable overlap windows that honor work hours, holidays, and focus blocks. Gatekeeper then refines what each viewer can see based on identity and policy. For high‑stakes moments—launches, incidents, or quarter‑end surges—Gatekeeper coordinates with Timeglue’s Surge controls to expand windows temporarily with approvals, cap exposure, and automatically sunset back to normal patterns. Teams retain velocity without making “temporary” exceptions permanent. Implementation is zero‑drama. With Tier Sync and Quick Connect, organizations can import roles, groups, and account tiers in minutes and map them to Gatekeeper policies. A domain‑based setup recognizes common defaults, and Starter Kits provide recommended templates by role and industry. Timeglue’s Masked Blocks and Safe Propose keep external viewers productive; they can request times within open windows without seeing exact availability, while Timeglue confirms slots privately and returns holds or invites. For compliance and security teams, Gatekeeper Links provide clarity instead of guesswork. The Audit Ledger supports one‑click CSV or JSON exports for auditors. Policies are versioned, changes are attributed, and every exception is paired with its recorded rationale. Timeglue encrypts data in transit and at rest and supports SSO and SCIM provisioning via leading IdPs. Availability and pricing: Gatekeeper Links are generally available today for Pro and Enterprise plans. Prime Hour Shield, Tier Sync, Quota Guard, Cap Bands, Audit Ledger, and Access Request are included at launch. Existing Timeglue customers can activate Gatekeeper in Admin Settings, with a guided setup that maps IdP/CRM attributes to policy rules. New customers can start a 14‑day trial at timeglue.com and be production‑ready in under an hour. About Timeglue: Timeglue is time zone scheduling software that finds sane cross‑region meeting windows while honoring work hours, holidays, and focus blocks. Built for remote team leads, sales and CS pods, and global operations, Timeglue ends time zone math and back‑and‑forth with paintable timelines and smart links that prevent after‑hours invites. Call to action: See Gatekeeper Links in action at timeglue.com/gatekeeper or book a live demo with our product team. Media contact: Timeglue Communications press@timeglue.com +1 415 555 0139 www.timeglue.com/press

P

Timeglue Launches Culture Clock to Build Respectful Schedules Across Regions, Religions, and Routines

Imagined Press Article

San Francisco, CA — September 1, 2025 — Timeglue today launched Culture Clock, a new set of capabilities that helps global teams schedule with cultural care—beyond national holidays. Culture Clock understands daily observances, school runs, and regional rest patterns, and it steers suggestions toward humane, high‑attendance windows while preserving velocity. The result is fewer no‑shows, fewer last‑minute reschedules, and a scheduling culture people trust. Global work succeeds on trust, and trust is built when plans respect people’s lives. Culture Clock brings that respect into the scheduling workflow without exposing sensitive details. Respect Profiles let teammates opt into preferences—observance windows, Fridays off, school runs—expressed as patterns rather than personal data. Observance Aware detects daily and weekly observance windows by locale and season and quietly avoids them, proposing nearby alternates with optional micro‑buffers for preparation or travel. School Run Shield protects family logistics with configurable buffers and one‑off exceptions for special events, so recurring series stay stable without erasing what matters. Different regions operate on different rhythms. Siesta Sense recognizes split‑shift cultures and marks midday hours as fragile, preferring morning or late‑afternoon overlaps that maintain energy and attendance. Culture Clock then synthesizes these signals into a single Respect Index that scores each proposed slot or series on cultural respect, explaining what drives the score and offering one‑click improvements. Paired with Timeglue’s FairShare suite, leaders can balance both equity and respect across time zones—two goals that often conflict—while keeping legal and workload guardrails intact. “We built Culture Clock because the world’s calendars are richer than a list of public holidays,” said Maya Ingram, CEO of Timeglue. “Our customers want velocity with dignity. Culture Clock weaves local customs into scheduling so teams move fast without stepping on what’s important to people.” Early adopters report smoother coordination and fewer escalations. “We run training and care programs across North Africa, the Middle East, and Europe,” said Laila Benhaddou, Program Director at Marigold Health Network. “Culture Clock’s observance awareness and Respect Index changed the tone of our planning meetings. We don’t argue about time slots anymore—the system shows us respectful options and why they’re better. Attendance is up, and goodwill is up.” Culture Clock integrates directly with Timeglue’s Heatmap Canvas. Leaders paint acceptable overlap windows that honor work hours, holidays, and focus blocks. The canvas then overlays cultural sensitivity signals, flagging fragile periods with Why Sensitive explanations: the specific observance, regional rest, or family window in play, plus instant alternates that resolve the issue with minimal shift. Polite Context adds a brief note to invites and smart links—localized by region and language—explaining how the chosen time respects local customs, building trust with customers and candidates without exposing personal information. Privacy is foundational. Culture Clock is preference‑based and consent‑driven; organizations can set reasonable defaults by locale while individuals opt in and fine‑tune their own Respect Profiles. Sensitive patterns are represented as scheduling constraints, not as raw calendar details. Timeglue’s privacy controls—including Masked Blocks, Trust Tiers for graduated visibility, and Ghost Ledger for access logging—ensure that external partners see only the minimum necessary detail to keep work moving. For operations leaders, Culture Clock offers measurable outcomes. The Respect Index and conflict explanations appear wherever times are proposed—on the canvas, in smart links, and during recurring series planning—so teams can quantify improvements and defend choices to stakeholders. Combined with Timeglue’s FairShare and Legal Limits, leaders can demonstrate that they evaluated equity, cultural respect, and compliance together, with clear rationales for each decision. Availability and pricing: Culture Clock is available today on Timeglue Pro and Enterprise plans. Respect Profiles, Observance Aware, School Run Shield, Siesta Sense, Respect Index, Polite Context, and Why Sensitive launch globally with localized templates for English, French, Spanish, and Arabic. Additional languages and regional observance sets are in development. Existing customers can enable Culture Clock from Admin Settings; new customers can start a 14‑day trial at timeglue.com and experience Culture Clock during onboarding. About Timeglue: Timeglue is time zone scheduling software that finds sane cross‑region meeting windows while honoring work hours, holidays, and focus blocks. Built for remote team leads, NGOs, agencies, and global enterprises, Timeglue ends time zone math and back‑and‑forth with paintable timelines, smart links, and safeguards that prevent after‑hours invites. Call to action: Explore Culture Clock and download the cultural respect playbook at timeglue.com/culture. Media contact: Timeglue Communications press@timeglue.com +1 415 555 0139 www.timeglue.com/press

P

Timeglue Rolls Out Zero‑Touch Onboarding to Make Global Scheduling Ready on Day One

Imagined Press Article

San Francisco, CA — September 1, 2025 — Timeglue today announced Zero‑Touch Onboarding, a streamlined setup experience that gets new organizations ready to schedule respectful cross‑region meetings in minutes—not weeks. By recognizing your domain, connecting to your HRIS and IdP in one click, and auto‑assigning time zones and holidays, Zero‑Touch Onboarding eliminates CSV wrangling and guesswork so teams can publish sane booking windows on day one. Zero‑Touch Onboarding is a response to a common barrier: leaders want better scheduling guardrails, but the initial setup feels steep. Timeglue collapses that lift with a sequence of smart automations that still keep admins in control. Domain Snap instantly recognizes a company domain and pre‑configures the right integrations for Google or Microsoft calendars, Okta or Azure AD, and popular HRIS tools. Quick Connect imports teams, managers, locations, and work‑hour policies read‑only, creating an accurate roster in minutes while preserving source‑of‑truth governance. From there, Locale Mapper assigns each person’s time zone and holiday set using HRIS location, office assignment, and calendar locale, flagging anomalies for quick review. Starter Kits provide role‑ and industry‑based presets such as Engineering standups, Sales pods, CS onboarding, and Recruiting panels. Each kit pre‑paints safe overlap windows and focus‑block defaults that reflect real‑world patterns, so leaders can start with a credible plan instead of a blank canvas. Before any invite goes out, Risk Precheck runs a fast simulation to surface after‑hours exposure, holiday conflicts, and compliance risks, with one‑click fixes that keep the plan clean. “New customers shouldn’t have to build a scheduling engine before they can use one,” said Dev Patel, VP of Product at Timeglue. “Zero‑Touch Onboarding gives you an instant baseline that respects work hours, holidays, and focus time, and then gets smarter as your org data updates. You’re productive on day one, and safer on day two.” Zero‑Touch doesn’t end at launch. Drift Guard watches HRIS and IdP changes during the first 30 days—new hires, team moves, location changes—and auto‑updates teams, hours, and holiday sets, sending a compact Slack or Teams digest so admins can approve or roll back with a click. Auto Announce generates SSO‑gated smart links and posts a welcome brief to Slack or Teams, explaining booking windows and guardrails so everyone knows where to book and what to expect. The net effect is a calm rollout: fewer meetings about meetings, fewer after‑hours surprises, and a shared understanding of how global scheduling works at your company. Zero‑Touch Onboarding aligns with Timeglue’s privacy and security posture. The system imports only the attributes required for scheduling, keeps HRIS and IdP connections read‑only by default, and logs every change. Masked Blocks publish availability as open or closed windows at configurable granularity to external partners, while Trust Tiers reveal just enough detail based on identity. Safe Propose lets recipients request times inside windows without seeing exact schedules, and Ghost Ledger maintains an access trail for compliance teams. Early users report that Zero‑Touch removes the fear of implementation debt. “We stood up Timeglue across 220 people in an afternoon,” said Aaron Cho, Director of Operations at RelayFoundry. “Domain Snap and Quick Connect did the heavy lifting, Locale Mapper caught three location mismatches we never would’ve noticed, and the Starter Kits gave us a realistic starting point. The Auto Announce post answered 90% of questions before they came in.” Availability and pricing: Zero‑Touch Onboarding is available today to all new Timeglue customers and will roll out to existing organizations over the next two weeks. Domain Snap, Quick Connect, Locale Mapper, Starter Kits, Risk Precheck, Drift Guard, and Auto Announce are included with all paid plans. Admins can enable Slack or Teams digests and Auto Announce during setup. For enterprise environments, SCIM provisioning and custom attribute mapping are supported on the Enterprise plan. About Timeglue: Timeglue is time zone scheduling software that finds sane cross‑region meeting windows while honoring work hours, holidays, and focus blocks. Built for remote team leads at startups, agencies, and global enterprises, Timeglue ends time zone math and back‑and‑forth with paintable timelines and smart links that prevent after‑hours invites. Call to action: Start a 14‑day trial and experience Zero‑Touch Onboarding at timeglue.com/start. Media contact: Timeglue Communications press@timeglue.com +1 415 555 0139 www.timeglue.com/press

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.